Two GitHub accounts on one machine? Let one gh wrapper hand git the right token

If you keep a personal and a work GitHub account on the same machine, you have probably hit this: gh auth login both accounts, run gh auth setup-git, and half your repos start returning Repository not found. That's because gh auth git-credential always answers with the keyring's active account. It never looks at which repo git is asking about.

The fix is to put a small wrapper in front of the real gh. The wrapper picks a token by repo owner, and git uses the same wrapper as its credential helper. Now both gh and plain git go through one place that knows about your accounts.

The heart of the wrapper is a few lines of bash (save as ~/bin/gh-route, keep the real gh on PATH):

#!/usr/bin/env bash
# Pick a token by owner, then run the real gh with it.
owner_from() { sed -E 's#^(https://github.com/|[email protected]:)##; s#/.*##'; }

if [[ "$1 $2" == "auth git-credential" ]]; then
    req="$(cat)"                                   # git's request, on stdin
    owner="$(sed -n 's#^path=\([^/]*\)/.*#\1#p' <<< "$req")"
fi
[[ -n "$owner" ]] || owner="$(git remote get-url origin 2>/dev/null | owner_from)"

case "${owner,,}" in
    my-work-org|my-work-user) export GH_TOKEN="$GH_TOKEN_WORK" ;;
    ?*)                       export GH_TOKEN="$GH_TOKEN_PERSONAL" ;;
esac

if [[ -n "$req" ]]; then gh "$@" <<< "$req"; else gh "$@"; fi

Then wire git to it, and move everything to HTTPS:

# ~/.gitconfig
[credential "https://github.com"]
    helper =
    helper = !bash ~/bin/gh-route auth git-credential
    useHttpPath = true
[url "https://github.com/"]
    insteadOf = [email protected]:
    insteadOf = ssh://[email protected]/

useHttpPath is what makes this reliable. Without it git only tells the helper host=github.com, so the wrapper has to guess from the current directory, and git ls-remote <url> run outside a repo gets the wrong account. With it git sends path=<owner>/<repo>.git, so the owner is right there in the request. (git clone happens to work either way: git writes origin into the new repo and exports GIT_DIR before it asks for credentials.)

The insteadOf lines rewrite existing [email protected]: remotes on the fly, so you don't have to edit a single .git/config. HTTPS also gets through networks that reset SSH, and it's the only transport GitHub App installation tokens work with.

One trap: never run gh auth setup-git again. It appends its own [credential "https://github.com"] block after your include, and its empty helper = line silently wipes out the wrapper.

On Windows (Git Bash), call the wrapper through bash as shown instead of an .exe shim. Git spawning a shim from its own sh can die with a cygheap read copy failed fork error.

Comments

  1. Markdown is allowed. HTML tags allowed: <strong>, <em>, <blockquote>, <code>, <pre>, <a>.