gh auth manages the credentials gh uses, and — optionally — the credentials Git uses for pushing
and pulling.
Those are two separate things, and gh auth login can configure both. Understanding which is which
prevents a specific confusion: gh working perfectly while git push asks for a password.
The command family
Section titled “The command family”| Command | Purpose |
|---|---|
gh auth login | Authenticate to a host |
gh auth status | Show active account and state per host |
gh auth switch | Change the active account |
gh auth logout | Remove stored credentials |
gh auth refresh | Change the scopes on an existing credential |
gh auth token | Print the token gh is using |
Logging in interactively
Section titled “Logging in interactively”gh auth loginThe prompts ask four things, and each is worth understanding rather than accepting.
Which host. github.com, or a GitHub Enterprise Server hostname. gh stores credentials per
host, so you can be authenticated to several simultaneously.
Git protocol — HTTPS or SSH. This decides what gh configures for Git operations, and what URL
form gh repo clone produces. HTTPS with the credential helper is the simpler path; SSH suits a
machine where you already have keys. The trade-offs are in
Account Setup.
Whether to authenticate Git with your GitHub credentials. Saying yes configures Git’s credential
helper to use gh, so git push works without a separate token. This is the step that, skipped,
produces the “gh works but git does not” confusion.
How to authenticate — browser or token. The browser flow is the default and is preferable
interactively: it produces a credential scoped by gh and never exposes a token you might paste
somewhere.
Checking state
Section titled “Checking state”gh auth statusWhat it doesReports, per host, which account is active, how the credential is stored, the Git protocol, and the token's scopes.
Why we run itThis is the first diagnostic for anything authentication-related. Most 'gh is broken' reports are a wrong active account or a missing scope, and both are visible here.
Expected resultOne block per host, with a tick for a valid credential.
Output:
github.com ✓ Logged in to github.com account octocat (keyring) - Active account: true - Git operations protocol: ssh - Token scopes: 'gist', 'read:org', 'repo', 'workflow'Two lines matter most. Active account determines what every subsequent command does. Token scopes determine what is permitted — a missing scope produces a 403 that looks like a permissions problem on the repository rather than on your credential.
Multiple accounts
Section titled “Multiple accounts”Work and personal accounts on one machine is common, and gh handles it directly.
gh auth login # log in to the second accountgh auth status # both listed; one marked activegh auth switch # interactive pickergh auth switch --user other-name # explicitThe active account applies to every gh command, which is a global mode with the usual hazard:
running a command believing you are one identity when you are another. Before anything consequential
in a shared or unfamiliar context, check gh auth status.
Scopes and refreshing
Section titled “Scopes and refreshing”Tokens carry scopes. If a command fails with a permissions error despite you having repository access, the credential probably lacks the scope rather than you lacking the right.
gh auth refresh --scopes repo,workflow,read:orggh auth refresh --scopes admin:orggh auth refresh re-authorises the existing credential with a new scope set rather than creating a
second login.
A common case: workflow files. Pushing a change to .github/workflows/ requires the workflow
scope, and without it the push is rejected with a message about workflow permissions that reads like
a repository policy problem.
Tokens and automation
Section titled “Tokens and automation”Interactive login is wrong for CI: there is no browser, and a stored credential on a shared runner is a liability. Use an environment variable instead.
gh reads GH_TOKEN, and falls back to GITHUB_TOKEN:
export GH_TOKEN="$MY_SECRET_TOKEN"gh pr list --repo OWNER/REPOWhen the variable is set, gh uses it and ignores stored credentials entirely. Nothing is written to
disk, which is exactly what you want on a machine you do not control.
In GitHub Actions, the workflow token is provided automatically and needs only to be passed through:
- name: Comment on the pull request env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh pr comment "$PR_NUMBER" --body "Build finished."Non-interactive login from a token on stdin is also possible, and is preferable to putting it in an argument:
echo "$MY_TOKEN" | gh auth login --with-tokengh auth token
Section titled “gh auth token”Prints the token gh is using, which is useful for handing credentials to another tool:
gh api repos/OWNER/REPO -H "Authorization: Bearer $(gh auth token)"Treat its output as a live credential. Piping it into a log, a file, or a command that prints its arguments is the same as publishing it.
Where credentials are stored
Section titled “Where credentials are stored”gh prefers your operating system’s keyring — Keychain on macOS, Secret Service on Linux, Credential
Manager on Windows. gh auth status reports which store is in use.
Where no keyring is available, as on many headless Linux servers, gh falls back to a plain file
under ~/.config/gh/. That file is readable by anything running as your user, which is a good reason
to prefer GH_TOKEN from a secret store on servers rather than a stored login.
Logging out
Section titled “Logging out”gh auth logoutgh auth logout --hostname github.com --user octocatThis removes the local credential. It does not revoke the token server-side — for a credential that may have been exposed, revoke it in your account settings as well.
Choosing an authentication method
Section titled “Choosing an authentication method”| Situation | Method |
|---|---|
| Your own workstation | gh auth login, browser flow |
| Second account on the same machine | gh auth login again, then gh auth switch |
| GitHub Actions | GH_TOKEN from the workflow token |
| Other CI | GH_TOKEN from a secret store |
| Shared or headless server | GH_TOKEN per invocation; avoid stored credentials |
| Long-lived organisation automation | A GitHub App, not a personal token |
The last row matters more as automation grows. A personal token ties automation to a person, expires with their account, and carries their permissions. Fine-grained tokens and GitHub Apps cover the alternatives.
Diagnosing authentication problems
Section titled “Diagnosing authentication problems”Almost every gh failure that looks mysterious is one of five things. In the order worth checking:
1. Which account is active?
gh auth statusWith two accounts configured, the active one applies to every command. Running a work command while your personal account is active produces 404s on repositories you can definitely see in a browser — because that account cannot see them.
2. Does the token have the scope?
The scopes line in gh auth status is the answer. Pushing a change to .github/workflows/ without
the workflow scope fails with a message about workflow permissions that reads like a repository
policy problem and is not.
3. Is it a 404 that means 403?
GitHub returns “not found” for private resources your credential cannot see. While debugging, treat 404 on something you believe exists as “wrong account or insufficient scope” first.
4. Is GH_TOKEN set and overriding your login?
env | grep -E '^GH_TOKEN=|^GITHUB_TOKEN=' | sed 's/=.*/=<set>/'An exported token in your shell profile silently takes precedence over gh auth login, which
produces the confusing situation where gh auth status shows one account and commands behave as
another. The command above confirms whether one is set without printing it.
5. Is the credential still valid?
gh api user --jq '.login'A 401 means expired, revoked, or malformed. Tokens with an expiry do expire, and the failure arrives without warning.
Enterprise Server
Section titled “Enterprise Server”gh works against GitHub Enterprise Server, with credentials stored per host:
gh auth login --hostname github.company.comgh auth statusgh repo list --hostname github.company.comexport GH_HOST=github.company.comSetting GH_HOST makes a shell session default to the enterprise instance, which is less
error-prone than remembering --hostname on every command. Note that API paths and feature
availability can differ from github.com — Enterprise Server runs a versioned release that lags the
hosted product, so a command working against github.com may not exist there.
Scopes worth understanding
Section titled “Scopes worth understanding”Classic token scopes are coarse, and a few come up constantly:
| Scope | Grants |
|---|---|
repo | Full control of all repositories the user can reach, public and private |
read:org | Read organisation membership and teams |
workflow | Update workflow files under .github/workflows/ |
gist | Create and edit gists |
delete_repo | Delete repositories |
admin:org | Full organisation control |
repo is the one to be wary of. It cannot be narrowed to a single repository and it includes write
access — so a token created for a read-only reporting script can, if leaked, push to everything its
owner can push to.
gh auth login requests a reasonable default set. Add scopes as you need them:
gh auth refresh --scopes repo,workflow,read:orgThis is also why fine-grained tokens exist, and why they are preferable for anything scripted: they scope per repository and per permission rather than per account-wide capability.
SSH keys through gh
Section titled “SSH keys through gh”gh can manage the SSH keys on your account, which removes a browser round trip when setting up a
new machine:
gh ssh-key listgh ssh-key add ~/.ssh/id_ed25519.pub --title "work laptop"gh ssh-key delete KEY_IDgh gpg-key listgh auth login also offers to generate and upload a key during setup, which is the fastest path on
a fresh machine — one command instead of ssh-keygen, copying the public half, and pasting it into
settings.
Titles matter more than they seem. A key list full of “id_ed25519” tells you nothing when you are trying to work out which of six entries belongs to a laptop you no longer own, and unaccounted-for keys are unaccounted-for access.
Common mistakes
Section titled “Common mistakes”Declining Git credential configuration during login. gh works, git push does not.
Not knowing which account is active. Run gh auth status before anything consequential.
Diagnosing a scope problem as a permissions problem. Check the scopes line first.
Storing credentials on a shared server. Use GH_TOKEN from a secret store.
Tokens in command arguments. Captured in history and visible to other processes.
Assuming logout revokes the token. It removes it locally only.
Using a personal token for team automation. It belongs to a person, not the team.
Token security in practice
Section titled “Token security in practice”gh auth token prints a live credential. A few habits keep it from leaking.
Never assign it to a variable that gets echoed. Command substitution into a command that logs its arguments defeats the purpose:
# Fine — the token goes straight into the headercurl -H "Authorization: Bearer $(gh auth token)" https://api.github.com/user
# Dangerous — visible in the process list, and in `set -x` outputTOKEN=$(gh auth token)some-tool --token "$TOKEN"Beware set -x. Shell tracing prints every command with its arguments expanded, including
tokens. Disable tracing around anything handling credentials:
set +xexport GH_TOKEN=$(gh auth token)set -xCheck what your shell history retains. Most shells record everything by default. A token typed
once is in ~/.bash_history indefinitely.
Treat GH_DEBUG=api output as sensitive. It prints request headers, which include the
authorisation header. Never paste it into an issue report without redacting.
Credential helpers and Git
Section titled “Credential helpers and Git”gh auth login can configure Git to authenticate through gh, which is what makes git push work
without a separate token. The mechanism is a credential helper entry:
git config --get-regexp 'credential.*helper'Output:
credential.https://github.com.helper !/usr/bin/gh auth git-credentialThat line tells Git to ask gh for credentials when talking to github.com. Removing it — or having a
different helper take precedence — produces the “gh works but git asks for a password” symptom.
gh auth setup-gitgh auth setup-git --hostname github.company.comgh auth setup-git (re)configures it, which is the fix when the helper is missing or was overwritten
by another tool. It is also what you run if you declined the Git configuration during the initial
login and later changed your mind.
Note the interaction with other helpers: if you also have osxkeychain or manager configured, Git
consults helpers in order and the first with an answer wins. Two helpers holding different credentials
for github.com produces intermittent, confusing authentication failures — worth checking when
diagnosing one.
Exercise
Section titled “Exercise”- Run
gh auth statusand read every line — note the storage backend, protocol and scopes. - Run
gh auth tokenand confirm it prints a credential. Do not paste it anywhere. - Unset your stored login in a subshell and authenticate with
GH_TOKENinstead, confirminggh repo viewstill works. - Run
gh auth refresh --scopes repo,workflowand compare the scopes line before and after. - If you have a second account, add it and practise
gh auth switch.
Step 3 is the pattern every CI job uses, and doing it once locally makes CI authentication unmysterious.
What you learned
Section titled “What you learned”gh auth logincan configure bothgh’s credentials and Git’s, and declining the second causes a common confusion.gh auth statusis the first diagnostic; active account and token scopes explain most failures.- The active account is global state — check it before anything consequential.
GH_TOKENoverrides stored credentials entirely, which is what makes CI work without writing to disk.- Credentials go to the OS keyring where available and to a plain file where not.
- Logout removes a credential locally; only revocation invalidates it.
The short version
Section titled “The short version”gh auth status is the first diagnostic for anything authentication-related. Active account and token
scopes explain the large majority of confusing failures, and the rate-limit trick — a limit of 60
means unauthenticated — settles the rest in one call.
The two things most often missed: declining Git credential configuration during login, which produces
“gh works but git asks for a password”; and an exported GH_TOKEN silently overriding a stored login,
so gh auth status reports one account while commands behave as another.
For CI, supply GH_TOKEN from a secret store rather than logging in — nothing is written to disk,
which is what you want on a machine you do not control.
Credential storage, chosen explicitly
Section titled “Credential storage, chosen explicitly”gh auth login stores credentials in the OS keyring where one is available and falls back to a plain
file where it is not. Both behaviours can be forced.
gh auth login --insecure-storagegh auth status✓ Logged in to github.com account octocat (keyring)✓ Logged in to github.com account other-user (/home/you/.config/gh/hosts.yml)The parenthesised value is the storage backend, and it is worth reading. keyring means the operating
system’s secret store. A path means a plain text file readable by anything running as your user.
--insecure-storage exists for headless Linux servers where no Secret Service daemon is running and
the keyring simply is not available. Using it is a deliberate trade, and on a machine you share it is
the wrong one — prefer GH_TOKEN supplied per invocation from a secret store, so nothing is persisted
at all.
Where a keyring exists but gh fell back to a file anyway, the usual cause is a headless session with
no unlocked keyring — a container, an SSH session, or a CI runner. That is worth noticing rather than
ignoring: a credential you believed was in a keyring sitting in a world-readable-to-your-user file is
the kind of assumption that turns into an incident.