:bulb: Work account, personal account, organization bot account — handling multiple GitHub accounts on one machine is surprisingly common. The question is whose credentials git sends when you git push. Using gh CLI as your credential helper means one gh auth switch line changes the push subject. This guide covers the principles and pitfalls.

:memo: This post targets macOS/Linux. On Windows, Git Credential Manager (GCM) is the default — helper names differ, but the concept is the same.


[01] Why Multi-Account Operations Matter

Git itself doesn’t store credentials. It delegates to an external program called a credential helper. Helpers can be the OS keyring, a plaintext file, or an external tool like GitHub CLI (gh).

Using gh as a helper makes multi-account management a one-liner. That one line is gh auth switch.


[02] How Credential Helpers Work

What happens during git push:

flowchart LR
    A["git push"] --> B{"remote URL
is HTTPS?"} B -->|Yes| C["Ask helper for
credentials"] C --> D["Helper responds
username + PAT"] D --> E["Send via
Basic Auth header"] E --> F{"GitHub
permission check"} F -->|OK| G["push succeeds"] F -->|NG| H["403 denied"] style G fill:#e8f5e9,stroke:#2e7d32 style H fill:#ffcccc,stroke:#c62828

Common helpers:

Helper Storage Notes
store ~/.git-credentials plaintext Simplest, weakest security
cache Memory (15min TTL default) Temporary, non-persistent
manager (GCM) OS keyring Windows standard
osxkeychain macOS Keychain macOS default
libsecret GNOME keyring Linux desktop
!gh auth git-credential gh provides dynamically Multi-account native

What makes gh helper special: instead of storing credentials itself, it dynamically provides the OAuth token of the active account. So gh auth switch changes who your git push goes as.


[03] Check Your System’s Helper

1
git config --get credential.https://github.com.helper

Sample output:

1
!/usr/bin/gh auth git-credential

If you see this, gh is managing your git credentials. From here on, gh’s active account = your git push account.

:bulb: If empty or shows store, osxkeychain, etc., gh isn’t registered as the helper yet. Register it below with gh auth setup-git.


[04] Adding a Second Account

Log in as the new account → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate.

Recommended permissions:

  • Repository access: Only repos you’ll use (not all)
  • Permissions: Contents = R/W, Workflows = R/W

:warning: The token is shown only once right after generation. Copy it immediately to a safe location (password manager).

4-2. gh auth login

1
gh auth login

Interactive flow:

Question Choice
Where do you use GitHub? GitHub.com
Preferred protocol for Git operations? HTTPS
Authenticate Git with your GitHub credentials? Yes
How would you like to authenticate? Paste an authentication token
Paste your authentication token (paste the PAT)

Success shows Logged in as <username>.

4-3. Verify Registration

1
gh auth status
1
2
3
4
github.com
  ✓ Logged in to github.com account work-bot
    Active account: true
  ✓ Logged in to github.com account personal-acct

The one with Active account: true is used for git push.


[05] Switching Active Account — gh auth switch

1
2
3
4
5
6
7
8
# Switch to second account
gh auth switch -u personal-acct

# Verify
gh auth status | grep -A1 "Active account"

# All subsequent git push goes as personal-acct
git push

:warning: gh auth switch applies machine-wide. It’s not per-repo or per-directory. Other working directories are affected too — get into the habit of running gh auth status to check the active account before starting work.


[06] Real Scenario — Pushing as Wrong Account

1
2
remote: Permission to org-a/repo.git denied to personal-acct.
fatal: unable to access 'https://github.com/org-a/repo.git/': 403

A clear signal — the active account doesn’t match the target repo’s owner.

Recovery:

1
2
3
4
5
6
7
8
# 1. Check current active account
gh auth status | grep "Active account"

# 2. Switch to the correct account
gh auth switch -u <correct-account>

# 3. Retry push
git push

[07] Verification Commands

When in doubt, diagnose with these three lines:

1
2
3
4
5
6
7
8
# Check helper
git config --list | grep credential

# gh registered accounts + active
gh auth status

# Simulate the credentials git actually receives (most powerful)
echo -e "protocol=https\nhost=github.com\n" | gh auth git-credential get

The last command prints the username/password git would receive at push time. Extremely useful for debugging — verifies in one shot whether the account switch took effect.


[08] Five Pitfalls

8-1. Switched, but Still Pushes as Old Account

Usually because username is baked into the remote URL:

1
2
git remote -v
# origin  https://old-user@github.com/...

URL username takes precedence over the helper. Clean it up:

1
git remote set-url origin https://github.com/<owner>/<repo>.git

8-2. PAT Expired

PATs have expiration dates. If push returns 401, suspect this first.

1
2
3
4
5
# Refresh
gh auth refresh -h github.com

# Or re-login
gh auth login

8-3. Need to Use Both Accounts Simultaneously

gh has only one active. For per-repo accounts:

Method Description Rating
A. Per-repo helper override git config --local credential.helper for a different helper ★★
B. SSH + ~/.ssh/config Host alias Map different SSH keys per host ★★★
C. PAT directly in remote URL Convenient but exposed in plaintext ★ (avoid)

SSH alias is usually cleanest.

8-4. macOS Keychain Conflicts with gh

macOS may auto-register osxkeychain. With both active, priority is unclear.

1
2
3
# Resolve conflict
git config --global --unset credential.helper
gh auth setup-git

8-5. Can’t Push to Private Fork

PAT scope insufficient, or Fine-grained PAT’s Repository access doesn’t include the fork. Add the fork to the access list in PAT settings.


[09] One-Page Summary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
git push auth essence
  └─ which credentials does the helper return?

Use gh as helper
  └─ 99% ends with gh commands
       ├─ add:      gh auth login
       ├─ switch:   gh auth switch -u <user>
       ├─ check:    gh auth status
       └─ refresh:  gh auth refresh

Top pitfall
  └─ Username in remote URL > helper
       → fix with git remote set-url

Machine-wide vs per-repo
  ├─ Machine-wide:  gh auth switch
  └─ Per-repo:      SSH alias / local helper override

:star: gh auth switch essentially reduces the cognitive load of GitHub multi-account operations to zero. The 30-second habit of running gh auth status before starting work removes 90% of 403 errors.