← back to blog
CI/CD passing · 17 June 2025 · 5 min read

The Pipeline That Didn't Fail — It Just Waited Forever

CI/CDConcourseGit

A pipeline task that clones a repository and its submodules started taking far longer than usual — not failing, just running well past its normal duration. The task logs stopped partway through a git clone, mid-submodule, with nothing after it. No error, no stack trace, no timeout message yet. Just silence.

The prompt nobody was there to answer

Attaching to the running task directly showed what the logs alone didn’t:

Cloning into 'automation'...
Username for 'https://internal-git-host/...':

git was waiting on stdin for a username. On a laptop, you’d type it and move on. Inside a CI worker with no TTY and nothing feeding stdin, that prompt is never going to be answered — the process just parks there until something external kills it (a task timeout, if one is configured — and if not, until someone notices).

Two separate credentials, one of them missing

The pipeline’s top-level repository checkout already had working credentials — the CI system’s git resource handled that automatically, and had for a long time without issue. The submodule inside that repository, though, pointed at a different host, and its credentials were a separate configuration value that hadn’t been threaded through to this particular task invocation. The top-level clone authenticated fine; the moment git recursed into the submodule and needed its own credentials, it had none, and — critically — it didn’t fail. It prompted.

That’s the part worth internalizing: a missing git credential doesn’t reliably produce an error. Depending on the credential helper configuration, it can just as easily produce an interactive prompt, and an interactive prompt inside a non-interactive environment isn’t a fast failure, it’s an indefinite hang.

The fix: make missing credentials fail loudly, not wait quietly

The reliable fix is forcing git into non-interactive mode explicitly, so a missing credential errors out immediately instead of prompting:

echo -e '#!/bin/bash\necho "${GIT_SUBMODULE_PASSWORD}"' > /tmp/git-askpass.sh
chmod +x /tmp/git-askpass.sh
export GIT_ASKPASS=/tmp/git-askpass.sh
export GIT_TERMINAL_PROMPT=0

git clone --recurse-submodules "$REPO_URL" landscape

GIT_TERMINAL_PROMPT=0 alone is worth adding to any CI environment as a blanket default — it turns every would-be hang of this shape into an immediate, readable authentication error. GIT_ASKPASS then supplies the actual credential without an interactive prompt at all, which is the real fix once you know the credential needs to be there.

The secondary lesson: two credential sets, one mental model

The deeper issue wasn’t really about the specific missing variable — it’s that a repository-plus-submodule setup has two credential surfaces, and CI git-resource tooling tends to make the top-level one easy (often automatic) while leaving the submodule one as something you have to remember to wire up yourself, separately, per pipeline. It’s an easy thing to get right once and then forget exists the next time a new pipeline is built from a template that assumes it’s already handled.

Why a hang is worse than a crash

A crash tells you something, immediately, in the build log, at the moment it happens. A hang tells you nothing until a timeout fires — and if no timeout is configured, it tells you nothing until someone notices the job has been “running” for six hours and goes looking. Every pipeline task that shells out to git (or anything else that can fall back to interactive auth) deserves GIT_TERMINAL_PROMPT=0 or the equivalent as a default, not a fix applied after the first time it eats an afternoon.