Published April 2026
Every few months somebody discovers, again, that Git authentication is a junk drawer full of legacy decisions, partial integrations, and optimistic documentation. The latest version of this ritual goes like this. You run gh auth login. GitHub CLI smiles. It tells you authentication is complete. You are logged in as the correct user. Then you run the most boring command in software engineering:
git clone https://github.com/ORG/REPO.git
And GitHub replies with:
remote: Write access to repository not granted. fatal: unable to access 'https://github.com/ORG/REPO.git/': The requested URL returned error: 403
At this point normal people start questioning their permissions, their operating system, and the moral legitimacy of HTTPS itself.
The annoying truth is simpler. gh being authenticated does not guarantee that plain old git over HTTPS is using the same credential. Those are related systems, not one system. On Linux in particular, they often drift apart in funny and infuriating ways.
The most confusing version of this failure is when all of the following are true at once:
gh auth login succeeds.gh repo view ORG/REPO works.git clone https://github.com/ORG/REPO.git still gets a 403.That feels impossible. But it is not impossible. It just means the browser, gh, and git are each authenticating differently. The browser is using your web session. gh is using its stored token, or thinks it is. git is using whatever credential path it happens to find first, which may be no credential at all, an invalid token, a stale token, or an environment variable somebody forgot about three weeks ago.
The fastest way to sabotage yourself is to have GH_TOKEN exported in your shell startup files, environment manager, desktop launcher, or some automation wrapper. GitHub CLI will happily notice it. In fact it will often prioritize it over interactive login. If that token is invalid, under-scoped, or just plain placeholder garbage like YOUR_TOKEN, you get a wonderful hybrid state where gh sort of looks configured while real operations fail.
Check for that first:
env | grep -E 'GIT|GH|SSH' # and gh auth status
If you see something like:
GH_TOKEN=YOUR_TOKEN X Failed to log in to github.com using token (GH_TOKEN) - The token in GH_TOKEN is invalid.
then congratulations, you have found the gremlin. Remove it from the current shell:
unset GH_TOKEN
Then log in again cleanly:
gh auth login
If you restart your terminal and the problem comes back, the variable is being re-exported somewhere, usually ~/.bashrc, ~/.profile, a shell plugin, or a launcher environment.
Even after you successfully log in with gh, plain git clone https://... may still fail. This is the part that feels like a personal insult, because it is exactly the job users think GitHub CLI was invented to do.
Conceptually, here is what is happening:
gh stores or accesses a GitHub token.git needs credentials for HTTPS transport.git does not magically borrow the gh session just because both tools have the word GitHub in their name.There is a command that sounds like it should bridge this gap:
gh auth setup-git
Sometimes it does. Sometimes it does not. Sometimes it quietly configures integration that is still defeated by some other environment problem. If you run it and cloning still fails, do not spend the afternoon arguing with reality. Move to a more direct test.
If this command works:
git clone "https://YOUR_USERNAME:$(gh auth token)@github.com/ORG/REPO.git"
then three important things are proven immediately:
gh token is valid for that repo.git is sourcing credentials, not in GitHub permissions.This is ugly, but it is a wonderful diagnostic. It turns a vague “GitHub is broken” feeling into a precise statement: the HTTPS transport works if you hand it the token directly.
GitHub loves returning:
remote: Write access to repository not granted.
during operations that are not trying to write anything. You are cloning. You want read access. Yet the error sounds like you tried to force-push to production while wearing oven mitts. This is one of those messages that is technically adjacent to the problem while being emotionally disastrous for troubleshooting.
When you see it during clone, read it as: “the authenticated identity being presented to GitHub is wrong, missing, invalid, or insufficient for this private repository.”
Here is the sequence I recommend because it narrows the problem fast instead of making you randomly jab commands into the terminal.
gh auth status.GH_TOKEN is present, run unset GH_TOKEN.gh auth login again.gh repo view ORG/REPO to confirm the GitHub CLI session can see it.gh auth setup-git.If step 8 works, stop doubting yourself. The account and token are fine. The local Git credential plumbing is the problem.
The explicit token URL is a tactical fix. It gets you unblocked. It is not the prettiest forever solution.
A cleaner long-term setup is one of these:
SSH is often less psychologically damaging once it is set up properly, but of course it comes with its own rite of passage: host key verification, missing keys, wrong keys, keychain weirdness, and the classic Permission denied (publickey).
So yes, HTTPS auth is messy. SSH auth is also messy. The real lesson is that Git has accumulated multiple credential mechanisms over time, and GitHub CLI only papers over part of that complexity.
If you are doing private GitHub work on Linux, assume there are four layers that can each fail independently:
Do not treat “I can see the repo on github.com” as proof that git clone will work. It proves almost nothing except that your browser cookies are fine.
Do not treat “gh says I am logged in” as proof that plain git is using the same identity. It often is not.
And if an explicit $(gh auth token) URL clone works while normal clone fails, you have permission to be annoyed. That is not user error. That is tooling fragmentation dressed up as a seamless developer experience.
When gh auth login succeeds but git clone still returns 403 on a private repo:
GH_TOKEN in the environment.gh repo view works.gh auth setup-git.https://USERNAME:$(gh auth token)@github.com/....If that last form works, the repo permissions are fine. Your Git credential path is the thing that needs fixing.
Which is a very Git sentence, unfortunately.