GitHub authentication
Everything else in the github family depends on this one. A push that asks for a password,
a gh that reports 404 on a repository that exists, a workflow that cannot be triggered:
all of it is authentication, and all of it looks like something else.
Establish the state first, then pick the path. Do not start by installing anything.
Where the token comes from
${GITHUB_TOKEN} is served by the LaRuche secrets vault. Read it from the environment and
nowhere else: not from a .env you found, not from a file the user pasted, not from shell
history.
Never print it. Not to confirm it is set, not in an error message, not in a command you echo before running. To check it exists, test its length or call an endpoint with it. The moment a token reaches the transcript it is in the provider request and in the logs, and it has to be revoked.
[ -n "${GITHUB_TOKEN}" ] && echo "token present" || echo "token absent"
Step 1: find out what you already have
git --version
gh --version 2>/dev/null || echo "gh absent"
gh auth status 2>/dev/null || echo "gh not authenticated"
git config --global credential.helper 2>/dev/null || echo "no credential helper"
| What you see | Go to |
|---|---|
gh auth status reports a logged-in account |
nothing to do, use gh for everything |
gh present, not authenticated |
Path C |
gh absent |
Path A or B, neither needs an install or admin rights |
Inside a script, source the bundled helper instead. It sets GH_AUTH_METHOD to gh,
curl or none, plus GITHUB_TOKEN, GH_USER, GH_OWNER, GH_REPO and
GH_OWNER_REPO when it is in a repository with a GitHub remote:
source skills/github-auth/scripts/gh-env.sh
It is sourced, so it cannot signal by exit code. Read GH_AUTH_METHOD: none means every
call after it will come back 401.
Path A: HTTPS with a personal access token
The path that works everywhere, including headless machines, with no install.
Get a token. Send the user to https://github.com/settings/tokens. Scopes:
repofor anything to do with code, pull requests and issues;workflowonly to touch GitHub Actions;read:orgonly for organisation repositories. Grant the least of these that covers the task.Fine-grained tokens work too and are better scoped, but they must list each repository explicitly and default to no permissions at all. A fine-grained token that returns 404 on a repository the user can see in the browser is usually missing that repository, not expired.
The token is displayed once. Have the user store it in the LaRuche vault, not in a file.
Let git remember it.
git config --global credential.helper store git ls-remote https://github.com/<owner>/<repo>.gitThe second command triggers the prompt. The username is the GitHub login; the password is the token, not the account password. Success prints a list of refs.
storewrites the token in clear text to~/.git-credentials. On a shared machine usegit config --global credential.helper 'cache --timeout=28800'instead, which keeps it in memory for eight hours and forgets it on reboot. On Windows,manageruses the Credential Manager and is better than either.Set the identity that will appear in commits.
git config --global user.name "<name>" git config --global user.email "<email>"Verify.
git ls-remoteon a private repository succeeds, andgit config --global user.emailprints what you set. A public repository proves nothing: it answers without any credentials at all.
Path B: SSH key
Better when the user pushes often, or juggles several accounts.
Look before generating. A second key for an account that already has one causes silent authentication against the wrong identity.
ls -la ~/.ssh/id_*.pub 2>/dev/null || echo "no key"Generate, if there is none.
ssh-keygen -t ed25519 -C "<email>" -f ~/.ssh/id_ed25519 -N "" cat ~/.ssh/id_ed25519.pub-N ""means no passphrase, which is what makes it usable unattended and is a trade-off the user should be told about, not one you make quietly for them.The user adds the PUBLIC key at https://github.com/settings/keys. Only the file ending in
.pub. If you are ever about to displayid_ed25519without the suffix, stop: that is the private key.Test, and read the answer.
ssh -T git@github.comSuccess is
Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.That sentence is the success case, in full. It exits non-zero, which is normal and not a failure.Route HTTPS remotes over SSH, so existing clones keep working:
git config --global url."git@github.com:".insteadOf "https://github.com/"
Path C: the gh CLI
# a desktop with a browser
gh auth login
# headless, using the vault token
echo "${GITHUB_TOKEN}" | gh auth login --with-token
gh auth setup-git
gh auth setup-git is not optional on the token path: without it gh is authenticated
but plain git push is not, which produces the confusing state where every gh command
works and every push asks for a password.
Verify with gh auth status, which names the account and the scopes it holds.
Without gh, without git
Any GitHub API call works with curl and the vault token:
curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/user
Success returns JSON with a login field. Bearer and the older token scheme both
work. This is the fallback the other github skills use when GH_AUTH_METHOD is curl.
Traps
- A public repository authenticates nothing. Verify against something private, or with
/user, which requires a real token. - The password prompt is asking for the token. GitHub removed password authentication years ago. Typing the account password fails with a message about authentication, not about passwords.
- A token in a remote URL leaks.
git remote set-url origin https://user:TOKEN@...works, and then the token sits in.git/config, in everygit remote -v, and in the output of half the commands in the other github skills. Prefer a credential helper. - Scope errors read like permission errors.
Permission to X deniedon a repository the user owns means the token lacksrepo, not that the account lacks rights. - An expired token fails identically to a wrong one. Check the expiry date before regenerating anything.
- Do not run
gh auth loginwithout arguments in an automated context. It is interactive, it will wait forever, and nothing will explain why.
Failure modes
git push asks for a username and password. No credential helper, or gh auth setup-git was never run. Set the helper, then push again; the prompt appears once more
and then never again.
fatal: Authentication failed with a helper configured. Stale credentials cached from
a token that has since expired. Clear them and re-authenticate:
printf "protocol=https\nhost=github.com\n\n" | git credential reject
remote: Permission to <repo> denied. The token is valid and under-scoped. Regenerate
with repo. For a fine-grained token, add this repository to its list.
ssh: connect to host github.com port 22: Connection refused. A network that blocks
port 22, which is most corporate and some mobile ones. Use SSH over 443 by adding to
~/.ssh/config:
Host github.com
Hostname ssh.github.com
Port 443
Two accounts on one machine, pushing as the wrong one. Give each a host alias in
~/.ssh/config with its own IdentityFile, and use git@work-github:owner/repo.git in
the remote. Global credential helpers cannot tell the accounts apart.
gh: command not found and no administrator rights. Do not try to install it. Path A
needs nothing beyond git, and every github skill here has a curl fallback.