GitLab CI Error Guide: 'fatal: could not read Username for https://gitlab.com' Job Token Clone Failures
Fix GitLab CI's 'could not read Username' and 'terminal prompts disabled' when cloning a private dependency repo: use CI_JOB_TOKEN, url.insteadOf, and submodule strategy.
- #gitlab-cicd
- #troubleshooting
- #errors
- #authentication
Exact Error Message
A job that clones or fetches another repository — a dependency, a submodule, or a private module — dies with one of these in the log:
Cloning into 'shared-lib'...
fatal: could not read Username for 'https://gitlab.com': No such device or address
fatal: could not read Username for 'https://gitlab.com': terminal prompts disabled
The wording differs by Git version (No such device or address vs terminal prompts disabled), but the cause is identical: Git wanted to prompt for credentials, the CI environment has no TTY, and GIT_TERMINAL_PROMPT=0 (set by the runner) turned the prompt into a hard failure.
What the Error Means
GitLab automatically clones the current project for you using the per-job CI_JOB_TOKEN. That credential is scoped to that one project. The moment your script reaches out to a different repository over https:// — git clone, git submodule update, go mod download, pip install git+https://..., composer install — Git sees a bare URL with no embedded credentials, falls back to its interactive credential helper, finds no terminal, and aborts.
It is not a network problem and not a permissions problem (yet). It is a missing-credentials problem: nothing in the request told GitLab who is asking.
Common Causes
- Cloning another private repo without credentials.
git clone https://gitlab.com/group/shared-lib.gitinside a job has no token attached. - A bare HTTPS URL instead of the job-token form. Hard-coded
https://gitlab.com/...URLs in scripts,go.modreplace directives, orrequirements.txt. - Submodules over HTTPS.
.gitmodulespins absolutehttps://gitlab.com/...URLs andGIT_SUBMODULE_STRATEGYpulls them with no auth. - Package managers pulling private repos. Go modules, pip VCS installs, Composer VCS repos, and npm git dependencies all shell out to Git over HTTPS.
- A deploy token or PAT is actually required. When the source is in another group, or you need write access,
CI_JOB_TOKENmay not be enough and a deploy token / Project Access Token is the right credential.
How to Reproduce the Error
Add a job that clones a second private project with a plain URL:
build:
stage: build
image: alpine/git
script:
- git clone https://gitlab.com/acme/shared-lib.git
- ls shared-lib
The current project clones fine (GitLab injected its token), but the explicit git clone of shared-lib fails:
$ git clone https://gitlab.com/acme/shared-lib.git
Cloning into 'shared-lib'...
fatal: could not read Username for 'https://gitlab.com': terminal prompts disabled
Diagnostic Commands
Confirm the token exists and the prompt is disabled, and inspect the URL Git is actually using:
# In a job: confirm the predefined variables are present (never echo the token itself)
echo "user=${CI_JOB_TOKEN:+set}" # prints "user=set" without leaking it
echo "GIT_TERMINAL_PROMPT=$GIT_TERMINAL_PROMPT" # runner sets this to 0
git config --list --show-origin | grep -i url # any insteadOf rewrite?
cat .gitmodules # absolute https submodule URLs?
user=set
GIT_TERMINAL_PROMPT=0
If .gitmodules contains url = https://gitlab.com/acme/..., that absolute URL bypasses any credentials GitLab set up for the main clone.
Step-by-Step Resolution
1. Clone with the job token in the URL
The job token authenticates as the synthetic user gitlab-ci-token:
build:
stage: build
image: alpine/git
script:
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/acme/shared-lib.git
2. Rewrite all HTTPS URLs once with url.insteadOf
Instead of editing every URL (handy for go mod, pip, Composer), rewrite the host globally at job start:
before_script:
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"
Now any tool that calls Git for https://gitlab.com/... transparently gets the token. For Go modules also export GOPRIVATE and GONOSUMCHECK as appropriate:
variables:
GOPRIVATE: gitlab.com/acme
3. Fix submodules with relative paths + strategy
Prefer relative submodule paths in .gitmodules so they inherit the parent’s credentials:
[submodule "shared-lib"]
path = shared-lib
url = ../shared-lib.git
Then let the runner clone them:
variables:
GIT_SUBMODULE_STRATEGY: recursive
GIT_SUBMODULE_FORCE_HTTPS: "true"
Relative URLs resolve against the parent project’s origin, so the job token GitLab already injected covers them — no hard-coded host.
4. Use a deploy token or PAT when the job token isn’t enough
For cross-group access or write operations, create a deploy token (Settings > Repository > Deploy tokens) and pass it as masked CI/CD variables:
before_script:
- git config --global url."https://${DEPLOY_USER}:${DEPLOY_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"
Prevention and Best Practices
- Never hard-code
https://gitlab.com/...in scripts,go.mod, or.gitmodules; use relative submodule URLs and a singleurl.insteadOfrewrite inbefore_script. - Always authenticate as
gitlab-ci-token:${CI_JOB_TOKEN}for read access to sibling projects — it expires with the job, so nothing leaks. - Mask
CI_JOB_TOKEN-derived values and neverechothe raw token; use the${VAR:+set}trick to confirm presence. - Set
GIT_SUBMODULE_STRATEGY: recursiveplusGIT_SUBMODULE_FORCE_HTTPS: "true"rather than fighting SSH key distribution in CI. - The free incident assistant can turn a clone-failure log into a likely-cause-and-fix suggestion. More patterns live in the GitLab CI/CD guides.
Related Errors
- remote: You are not allowed to download code from this project — once you do send the job token, a
403means the source project hasn’t allowlisted yours under Token Access. That is the next error you hit after fixing this one. Invalid CI config— a malformedbefore_scriptrewrite can break config parsing; see the Invalid CI config guide.Host key verification failed— the SSH equivalent when you clone overgit@instead of HTTPS.
Frequently Asked Questions
Why does the main project clone but my second git clone fails?
GitLab injects CI_JOB_TOKEN only for the project that owns the pipeline. Any additional repo you clone is a plain, unauthenticated request unless you embed credentials yourself.
Is it safe to put CI_JOB_TOKEN in the clone URL?
Yes — it is the documented pattern. The token is short-lived (valid only for that job) and GitLab masks it in logs. Do not echo it or write it to a file that becomes an artifact.
Why does go mod download fail even though my own git clone works?
Go shells out to Git for https://gitlab.com/... and never sees your one-off URL. Use url.insteadOf so every Git invocation gets the token, and set GOPRIVATE so Go skips the public proxy and checksum database.
Should I use a deploy token instead of the job token?
Use the job token for read-only access to projects in the same scope — it is automatic and self-expiring. Reach for a deploy token or Project Access Token only when you need cross-group access, write access, or a credential that outlives the job.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.