fatal: could not read Username for 'https://gitlab.com': No such device or address
Fix "could not read Username for gitlab.com" in CI: set git config insteadOf with CI_JOB_TOKEN, allow the job token, or use a deploy key over SSH.
fatal: could not read Username for 'https://gitlab.com': No such device or address
Incident Overview
Fix "could not read Username for gitlab.com" in CI: set git config insteadOf with CI_JOB_TOKEN, allow the job token, or use a deploy key over SSH.
medium fix·6 min read
By Vishvesh Patel·DevOps Engineer·Updated 21 September 2026
fatal: could not read Username for 'https://gitlab.com': No such device or address fatal: could not read Username for 'https://github.com': terminal prompts disabled remote: HTTP Basic: Access denied fatal: Authentication failed for 'https://gitlab.com/acme/private-lib.git/'
Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Find which repository is being fetched without credentials
The checkout of the project itself is authenticated by the runner. A second repository, usually a submodule or a dependency pulled during the build, is not, and that is almost always what fails.
• Rewrite HTTPS URLs to carry the job token
insteadOf applies the credential to every matching URL, including ones inside .gitmodules and package manifests that you cannot easily edit. It is the least invasive fix.
• For submodules, let the checkout handle them
Setting this variable makes GitLab's own checkout fetch submodules with the job's credentials already configured, rather than leaving a later git submodule update to authenticate on its own.
Git needed a username and had no way to ask. There is no terminal in a CI job, so instead of prompting it fails with No such device or address, or on newer versions terminal prompts disabled.
The message is about the absence of a prompt. The real problem is the absence of a credential.
grep -rn "url = " .gitmodules 2>/dev/null || echo "no submodules"
It is rarely the main checkout
GitLab's runner configures credentials for the project being built, so git clone of the project itself works. What fails is a second repository fetched during the job:
That URL carries no credentials, so the fetch is anonymous, and a private repository refuses it.
grep -rn "url = " .gitmodules
Fix 1: insteadOf with CI_JOB_TOKEN
CI_JOB_TOKEN is injected into every job and is scoped to that job's lifetime. Rewrite matching URLs to carry it:
This applies to every https://gitlab.com/ URL Git encounters, including ones inside .gitmodules and inside tooling you do not control, which is why it is the least invasive option.
The username is literally gitlab-ci-token. It is not your username and not a variable.
For GitHub Actions the equivalent is:
before_script:
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"CI_JOB_TOKEN has to be allowed
Since GitLab 16, a job token from project A cannot read project B by default. You allowlist it explicitly.
In the target project: Settings → CI/CD → Job token permissions, add the source project to the allowlist.
Without that, the token authenticates and is then refused, which looks like this:
That is a different message from the one at the top of this page, and it means you got past the missing-credential problem and hit an authorisation one.
remote: HTTP Basic: Access denied
Fix 2: Let the runner fetch submodules
Simplest when submodules are the issue:
The runner fetches them as part of its checkout, with credentials already set up. GIT_SUBMODULE_FORCE_HTTPS rewrites git@ URLs in .gitmodules to HTTPS so the job token applies, which saves changing the file.
This only covers submodules. A dependency fetched by a package manager still needs Fix 1.
variables: GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_FORCE_HTTPS: "true"
Fix 3: Deploy keys and SSH
For cross-project access that outlives a job, or another host entirely, a deploy key scoped to the one repository is better than a personal token.
Generate a key, add the public half as a deploy key on the target project, and put the private half in a masked, protected CI variable of type File:
Use a File-type variable rather than pasting the key into a normal one, which mangles newlines and produces invalid format errors that waste a lot of time.
ssh-keyscan at runtime trusts whatever answers. For anything sensitive, pin a known host key in the repository instead.
before_script: - mkdir -p ~/.ssh && chmod 700 ~/.ssh - cp "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
Never do this
Two problems. A personal token carries your entire access, so the job can reach every project you can. And the URL with the token embedded is written into .git/config and frequently into job logs, where masking does not always catch it.
If a personal token is genuinely unavoidable, use a project access token scoped to one project with read_repository only.
- git clone https://user:${MY_PERSONAL_TOKEN}@gitlab.com/acme/lib.gitGo modules
GOPRIVATE stops Go trying the public module proxy and checksum database for those paths, which otherwise fails with a 404 that has nothing to do with authentication and sends you looking in the wrong place.
variables:
GOPRIVATE: gitlab.com/acme/*
GOFLAGS: -mod=mod
before_script:
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"Making the failure clearer
By default Git hangs or errors obscurely when it wants a prompt. Make it explicit:
It fails immediately with terminal prompts disabled, which is a much clearer message than No such device or address and avoids a job that sits waiting until it times out.
variables: GIT_TERMINAL_PROMPT: "0"
A checklist
Frequently Asked Questions
Why does Git ask for a username in CI when it never does locally?
Locally a credential helper supplies the credential, or Git prompts you on the terminal. A CI job has neither: no helper is configured for the repository being fetched, and there is no terminal to prompt on, so Git fails with No such device or address or, on newer versions, terminal prompts disabled. The message describes the missing prompt, but the underlying problem is always the missing credential. Setting GIT_TERMINAL_PROMPT=0 makes the failure immediate and clearer.
What is CI_JOB_TOKEN and how do I use it?
It is a short-lived token GitLab injects into every job, valid only for that job's duration, used with the literal username gitlab-ci-token. The cleanest way to apply it is a git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/" rewrite, which makes it apply to every matching URL including ones inside .gitmodules and inside package managers you do not control. Since GitLab 16 the target project must also allowlist the source project under job token permissions.
Why does my submodule fail when the main repository clones fine?
Because the runner configures credentials for the project being built and nothing else. A submodule URL in .gitmodules is fetched as a separate operation with no credentials attached, so a private submodule is refused. Either set GIT_SUBMODULE_STRATEGY: recursive so the runner's own checkout fetches them with credentials already in place, or apply an insteadOf rewrite before the submodule update runs. Adding GIT_SUBMODULE_FORCE_HTTPS also converts git@ URLs so the token applies.
Should I use a personal access token in CI?
No. A personal token carries your full access, so any job using it can reach every project your account can, and it usually outlives the reason it was created. It also tends to end up embedded in a clone URL, which is written into .git/config and often into job logs where masking does not reliably catch it. Use CI_JOB_TOKEN for same-instance access, a project access token scoped to one project with read_repository where that is not enough, or a deploy key.
What is the difference between this error and "HTTP Basic: Access denied"?
The first means no credential was supplied at all, so Git wanted to prompt and could not. The second means a credential was supplied and rejected, which in GitLab 16 and later most often means the job token is not allowlisted for the target project rather than that it is wrong. They are consecutive stages of the same problem, and moving from the first message to the second is actually progress: you have fixed the credential plumbing and now need to grant access.
Reference and practice
Learn the underlying concept
Other CI/CD errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.