Fastest fix: the cached credential is almost always stale. Erase it, then let Git re-prompt with a fresh token. On macOS run git credential-osxkeychain erase (paste the two lines below and press Return twice), then run git fetch and paste a new Personal Access Token when asked. That clears the most common case in under a minute. If that does not stick, work down the buckets below.
Typical symptoms: every git pull hangs ~30 seconds then fails with remote: HTTP Basic: Access denied or fatal: Authentication failed for 'https://github.com/...'; Git opens an empty credential prompt in a terminal that cannot accept interactive input (VS Code’s integrated terminal, an SSH session, a CI runner); or macOS Keychain Access shows the GitHub Internet password entry with a stale token that gets rejected on every operation. A credential helper bridges Git and your OS secret store. When that bridge breaks, nothing involving a remote works until you repair it.
Which bucket are you in?
| Symptom you see | Most likely cause | Jump to |
|---|---|---|
Authentication failed immediately, token was working last week | PAT expired or revoked | Cause 1, Step 1+2 |
| Worked before a password/token change, now fails | Stale cached credential | Cause 2, Step 1 |
| Re-prompts on every command, never caches | credential.helper points at a missing binary | Cause 3, Step 4 |
Permission denied (publickey) or hangs on SSH | SSH key not in the agent | Cause 4, Step 5 |
403 mentioning SAML / SSO / single sign-on | Org SSO not authorized for the PAT | Cause 5, Step 6 |
git push hangs forever, no output, on Windows | Git Credential Manager dialog stuck off-screen | Cause 6, Step 7 |
Common causes
Ordered by hit rate, highest first.
1. Personal Access Token (PAT) was revoked or expired
GitHub, GitLab, and Bitbucket let you set PAT expiry dates, and GitHub fine-grained tokens expire within a year by default. Once a token expires or is revoked, every operation returns 401/403. The helper faithfully replays the dead token, which the server rejects.
How to spot it: On GitHub, open Settings > Developer settings > Personal access tokens > Fine-grained tokens (or Tokens (classic)). If the token reads “Expired” or is missing, that is the cause. The server message is usually fatal: Authentication failed.
2. The OS keychain has a cached bad credential
After a password change or token rotation, the keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service) still hands Git the old credential. Git gets a 401, tries to re-prompt, and if the terminal cannot show a prompt it hangs or fails.
How to spot it: On macOS, open Keychain Access (Spotlight > type Keychain Access), search for github.com, and look at the Internet password entry — it holds the old token. On Linux, run secret-tool search server github.com.
3. credential.helper points at a binary that does not exist
After a macOS upgrade or a Homebrew update, git-credential-osxkeychain or git-credential-manager may have moved, or an old config may still hard-code a stale absolute path. Git silently falls back to re-prompting on every operation.
How to spot it: git config --global credential.helper returns a value. If it is an absolute path, run that path directly or which <helper-name>; “not found” confirms the helper is missing or moved.
4. SSH key was not added to the SSH agent
You switched from HTTPS to SSH remotes (git@github.com:...), but the key is not loaded in ssh-agent. Every SSH operation asks for the passphrase in a context where interactive input is unavailable.
How to spot it: ssh -T git@github.com returns Permission denied (publickey) or hangs; ssh-add -l returns The agent has no identities.
5. Corporate SSO requires authorizing the PAT
GitHub organizations with SAML single sign-on require each PAT (and SSH key) to be explicitly authorized for that org. A token that has not been SSO-authorized fails with a 403 whose message mentions SAML SSO — for example The 'Authorization' request header field is not allowed or you must use a personal access token (classic) ... authorized for the organization.
How to spot it: The error references “SAML” or “single sign-on.” On the token’s page under Settings > Developer settings, you will see a “Configure SSO” control next to the organization name.
6. Git Credential Manager is deadlocked on Windows
Git Credential Manager (GCM) on Windows can open an auth dialog behind the terminal window or on a different desktop session, blocking the terminal indefinitely while it waits for input.
How to spot it: git push hangs with no output for more than 30 seconds. Switching desktops or scanning the taskbar reveals a hidden authentication window.
Shortest path to fix
Step 1: Erase the stale cached credential
This is the single highest-yield action. Run the line for your platform, then press Return on the blank line so the helper reads to end of input:
# macOS — osxkeychain helper (GitHub's documented form)
git credential-osxkeychain erase
host=github.com
protocol=https
# (press Return on the empty line)
# Cross-platform — Git Credential Manager
git credential-manager erase <<'EOF'
protocol=https
host=github.com
EOF
# Generic — works with whatever helper is configured
git credential reject <<'EOF'
protocol=https
host=github.com
EOF
# Linux — libsecret / gnome-keyring
secret-tool clear server github.com protocol https
If the command prints nothing, it worked.
Step 2: Generate a new PAT and store it
Go to GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens > Generate new token. Scope it to the repositories you need and grant Repository permissions > Contents: Read and write (for read-only clones, Read works). Fine-grained tokens do not use the old repo checkbox — that scope only exists on classic tokens. Then trigger a prompt:
git fetch origin
# Username: your GitHub username
# Password: paste the new PAT (NOT your account password)
The helper caches the new token on first success, so you only enter it once.
Step 3: Clear duplicate / conflicting keychain entries (macOS)
If multiple github.com entries exist (common after switching accounts), Git may keep grabbing the wrong one:
# List the entry
security find-internet-password -s github.com
# Delete a specific account's entry, then re-run Step 1+2
security delete-internet-password -s github.com -a <account-name>
Step 4: Fix a missing or wrong helper binary
git config --global credential.helper # show current value
# If it returns a path that doesn't exist, reinstall GCM:
brew install --cask git-credential-manager # macOS / Linux (Homebrew cask)
# or
winget install --id Git.GCM # Windows
git config --global credential.helper manager # re-register GCM
Note: the helper was renamed from manager-core to manager in GCM 2.0.877, and recent releases dropped the old manager-core symlink entirely, so a stale config can now fail with git: 'credential-manager-core' is not a git command. If your config still reads manager-core, switch it to manager. On macOS, osxkeychain is also a valid value if you prefer the built-in helper.
Step 5: Load the SSH key into the agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh -T git@github.com # expect: "Hi <username>! You've successfully authenticated..."
To persist across sessions, add to ~/.ssh/config:
Host github.com
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
UseKeychain yes is macOS-specific — drop it on Linux/Windows.
Step 6: Authorize the PAT for SSO
- Go to Settings > Developer settings > Personal access tokens and click the token name.
- Find the “Configure SSO” control and click it.
- Click “Authorize” next to each organization that enforces SSO.
- Complete the SSO flow in the browser, then retry the Git operation.
If you use SSH instead of a PAT, the same authorization is done under Settings > SSH and GPG keys > “Configure SSO” next to the key.
Step 7: Break a deadlocked GCM (Windows / off-screen dialog)
# Windows (PowerShell)
Stop-Process -Name "git-credential-manager" -Force
# macOS / Linux
pkill -f git-credential-manager
Then rerun your Git command. If GCM keeps trying to open a browser/GUI on a headless box, force file storage instead:
git config --global credential.credentialStore plaintext # or use a PAT via env var
Step 8: Confirm it’s fixed
git fetch --dry-run origin
git push --dry-run origin main
Both should finish in a second or two with no prompt and no hang. A clean exit code (echo $? returns 0) plus no Authentication failed line means the helper is healthy again.
Prevention
- Prefer SSH
ed25519keys over HTTPS PATs on interactive developer machines — SSH keys do not expire by default and need no credential-helper config. - If you stay on PATs, set a calendar reminder ~7 days before expiry so you rotate before the token dies.
- Let
gh auth login(the GitHub CLI) manage your credentials when you can; it handles token storage, refresh, and SSO authorization more reliably than hand-editing config. - In CI, authenticate with an environment variable (
GITHUB_TOKEN, a deploy key, or a GitHub App installation token) instead of an interactive credential helper. Helpers are built for humans, not runners. - Set
credential.useHttpPath = truewhen you hold credentials for multiple orgs on the same host, so the helper stores one credential per repo path instead of one per host. - Pin the helper in a team Brewfile so everyone resolves it to the same managed path, and document the per-OS setup commands in onboarding.
FAQ
Q: Is git config credential.helper store safe? It saves the password in a file.
A: No. The store helper writes credentials in plaintext to ~/.git-credentials, readable by any process running as your user. Use osxkeychain (macOS), Windows Credential Manager, or GCM instead — they store the token in the OS’s encrypted keystore.
Q: How do I avoid credential prompts entirely in CI/CD?
A: Inject the token into URLs: git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/". This is fine in an ephemeral runner; never do it on a developer machine, where it leaks the token into config.
Q: How do I use two GitHub accounts on one machine?
A: SSH is cleanest. Add two Host aliases in ~/.ssh/config (github-work, github-personal) each pointing at a different IdentityFile, then write remotes as git@github-work:org/repo.git. For HTTPS, set credential.useHttpPath = true so each repo path stores its own credential.
Q: What do git credential approve and reject actually do?
A: git credential approve writes a credential (username + token) into the configured helper; git credential reject erases the stored credential for the given host/protocol. Both read the credential spec from stdin, which is why the erase commands above pipe host=/protocol= lines in.
Q: After I rotated the PAT, old CI jobs still fail. Why? A: CI stores secrets separately from your local helper. Update the secret in every CI store (GitHub Actions secrets, GitLab CI/CD variables, Jenkins credentials) and re-run the failed pipelines — your local fix does not touch them.