Two GitHub accounts on one Windows box, HTTPS only? Don't let `gh` be your git credential helper
If you run gh auth setup-git, the GitHub CLI becomes git's credential helper — and it only ever hands out the token for the active account. The moment you git pull a repo belonging to your other account, you get the wrong token (403, or commits land under the wrong identity). There's no per-repo routing; you'd have to gh auth switch every single time you cross accounts.
You can prove it to yourself — ask the helper for the non-active account and it returns nothing:
"protocol=https`nhost=github.com`nusername=second-account`n`n" |
& gh auth git-credential get # exit 1, no token
The fix is to hand git over to Git Credential Manager (it already ships with Git for Windows). GCM picks the token by the username embedded in the remote URL, so routing becomes automatic. gh keeps working for gh issue/gh pr — it uses its own keyring, separate from the git helper.
Switch the helper and point GCM at GitHub:
git config --global --unset-all credential.https://github.com.helper
git config --global credential.helper manager
git config --global credential.https://github.com.provider github
git config --global credential.guiPrompt false # terminal prompts, no GUI popup on a server
Now the nice part: you don't need a browser OAuth dance. If both accounts are already logged into gh, reuse those tokens and seed them straight into GCM:
$gh = "$env:ProgramFiles\GitHub CLI\gh.exe"
foreach ($acct in 'first-account','second-account') {
$tok = (& $gh auth token -u $acct).Trim()
"protocol=https`nhost=github.com`nusername=$acct`npassword=$tok`n" | git credential approve
}
The one rule that makes it all work: put the account username in every remote URL.
git remote set-url origin https://[email protected]/<org>/<repo>.git
git clone https://[email protected]/<user>/<repo>.git
To bulk-fix a whole folder of repos (handles SSH remotes too — converts them to HTTPS in passing), set $user to the account those repos belong to:
$user = 'first-account'
Get-ChildItem D:\path\to\repos -Directory -Recurse -Depth 1 |
Where-Object { Test-Path (Join-Path $_.FullName '.git\config') } |
ForEach-Object {
Push-Location $_.FullName
$url = git remote get-url origin 2>$null
$new = $null
if ($url -match '^git@github\.com:(.+)$') { $new = "https://[email protected]/$($Matches[1])" }
elseif ($url -match '^https://github\.com/(.+)$') { $new = "https://[email protected]/$($Matches[1])" }
if ($new) { git remote set-url origin $new; "fixed: $($_.Name)" }
Pop-Location
}
Verify without fetching anything: run git ls-remote --heads origin in any repo — exit 0 means it authenticated as the right account.
One gotcha: the tokens you seeded are gh OAuth tokens (gho_…), which can be rotated or revoked. If auth starts failing later, just re-run the seed loop, or let GCM do its own browser OAuth on the next push. For unattended boxes, seed a fine-grained PAT instead of the gh token.
If you also want commit identity to follow the account automatically, group each account's repos under its own root and let includeIf swap your email by directory:
# ~/.gitconfig
[user]
name = Your Name
email = [email protected] ; default
[includeIf "gitdir:C:/path/personal/"]
path = .gitconfig-personal
# ~/.gitconfig-personal
[user]
email = [email protected]
Forward slashes, trailing slash on the gitdir: path — both matter on Windows.