Run git commands with a specific identity profile.
git-as personal clone git@github.com:user/repo.git
git-as work push origin main
git-as personal commit -m "Fix bug"git-as is the runtime side of the identity tools: it loads a profile (managed by git-id), wires the right SSH key and authorship into the environment, and execs git. The current process is replaced — git-as itself doesn't sit in the call chain after exec.
# 1. Set up a profile (interactive)
git-id add personal
# 2. Use it
git-as personal status
git-as personal push -u origin some-branch
# 3. Inspect what's running
git-id agent listProfile names are arbitrary; personal/work is just a common pattern.
The hard problem git-as solves is picking a specific SSH key when the system ssh-agent has multiple keys loaded. Stock OpenSSH does not provide a "use the agent but only key X" directive: agent-loaded keys outrank -i regardless of IdentitiesOnly=yes. Without active isolation, the wrong key is offered first, and the remote authenticates as the wrong account — usually surfacing as a confusing Permission denied to <other-user> error.
git-as defaults to a per-profile managed sub-agent: a separate ssh-agent process whose only loaded identity is the profile's key. The system agent stays untouched (plain git push, plain ssh github.com, every other tool: same as before), but anything routed through git-as is force-fed the profile's key.
parent shell ssh-agent (system)
│ ├── work-key
│ └── personal-key
▼
git as personal push
│
├── identity.Ensure ───── ssh-agent ───────────► sub-agent /tmp/gtb-501/personal.sock
│ (only personal-key)
│
└── exec git, with:
GIT_SSH_COMMAND = ssh -i ~/.ssh/personal -o IdentitiesOnly=yes \
-o IdentityAgent=/tmp/gtb-501/personal.sock
The sub-agent persists across git-as invocations on purpose — that's the passphrase cache. First invocation with an encrypted key prompts once; subsequent invocations are silent until the agent is killed or the machine reboots.
Each profile gets its own sub-agent:
| File | Role |
|---|---|
<dir>/<profile>.sock |
UNIX socket the sub-agent listens on |
<dir>/<profile>.pid |
sub-agent's PID, written when spawned |
<dir>/<profile>.lock |
advisory flock for spawn-and-load critical section |
<dir> is normally ${XDG_CACHE_HOME:-~/.cache}/git-this-bread/agents/. If that path would push the socket past the ~104-byte sun_path limit (mostly a problem on macOS with deep $TMPDIR), git-as falls back to /tmp/gtb-<uid>/.
- Probe the socket via
ssh-add -l. Alive → check whether the profile's pubkey is loaded. - Lock
<profile>.lockif anything needs to change, to keep concurrent invocations from racing. - Spawn a new
ssh-agentif the probe failed. Load the profile's key withssh-addif it isn't already loaded. - Set
GIT_SSH_COMMANDwithIdentityAgent=<sock>and execgit.
Steady state — agent already running with key loaded — costs one ssh-add -l call (sub-millisecond).
| Platform | Behavior |
|---|---|
| macOS | ssh-add --apple-use-keychain is passed automatically. First-ever invocation prompts; passphrase is cached in Keychain; every subsequent invocation is silent — including across reboots. |
| Linux with gnome-keyring / ksshaskpass | We inherit the user's SSH_ASKPASS chain, so the passphrase is cached by the same daemon that handles the system agent. Silent across reboots once configured. |
| Linux without a keystore | One passphrase prompt per agent restart. The sub-agent persists across git-as invocations, so within a session it's "prompt once, cached forever." Reboot → prompt once again. |
If your encrypted key prompts more often than expected, see Troubleshooting below.
Set usecustomagent = false on a profile:
git-id set personal usecustomagent falseIn that mode, git-as does not spawn or talk to a sub-agent. It sets GIT_SSH_COMMAND="ssh -i <key> -o IdentitiesOnly=yes" and lets whatever ssh-agent your shell session already has — SSH_AUTH_SOCK, whether that's the OS's launchd/systemd ssh-agent, 1Password, KeePassXC, gpg-agent, or one you started by hand — take over.
Use this when:
- Your system agent is itself per-key isolated (1Password, KeePassXC, gpg-agent with destination constraints) — wrapping it in a sub-agent is redundant.
- You're debugging an agent-related issue and want to bypass
git-as's management entirely.
When you opt out, the original SSH-agent ordering caveats apply: an agent-backed key registered on a different account on the same host can still authenticate as the wrong account. The whole point of the default sub-agent is to make that class of failure impossible by construction.
git-as sets these variables on the exec'd git (any colliding values from the parent env are dropped first, so a parent-set GIT_SSH_COMMAND can't silently survive execve):
| Variable | Value |
|---|---|
GIT_SSH_COMMAND |
ssh -i <key> -o IdentitiesOnly=yes -o IdentityAgent=<sock> (or without IdentityAgent when usecustomagent=false) |
GIT_AUTHOR_EMAIL |
profile email |
GIT_COMMITTER_EMAIL |
profile email |
GIT_AUTHOR_NAME |
profile name (display name) or user (fallback), if set |
GIT_COMMITTER_NAME |
same |
git-id agent list # show running sub-agents (alive/dead, PID, key count)
git-id agent kill <profile> # terminate a profile's sub-agent
git-id agent kill --all # terminate every sub-agent under the cache dir
git-id agent reload <profile> # re-run ssh-add against the existing sub-agentKilling is non-destructive — the next git-as invocation will respawn. Useful when:
- You rotated the profile's SSH key and want the agent to pick up the new one.
- Some other tool is misbehaving and you want a clean slate.
- You're worried about agent state crossing some trust boundary.
Run git-id agent list and git-id show personal. If the sub-agent has the right key but you're still getting the wrong account, double-check that usecustomagent isn't accidentally set to false:
git config --get-all identity.personal.usecustomagentEmpty output means the default (true) is in effect. If it prints false, that's why isolation isn't happening.
Check git-id agent list. If the agent dies between invocations, every fresh spawn re-prompts. Likely causes:
- Your shell or session manager is killing background processes on exit.
git-id agent killis being run somewhere unexpectedly.- The socket file got cleaned up by a
/tmpreaper (only relevant under the/tmp/gtb-<uid>/fallback).
On macOS, --apple-use-keychain should make this irrelevant — the prompt should appear only once ever. If it doesn't, your key may not actually be encrypted, or ssh-add may be using a different keychain entry than expected. security find-internet-password -s "ssh-agent" -a <key path> can confirm.
Should not happen with git-as — the /tmp/gtb-<uid>/ fallback handles long cache paths. If you see this anyway, your /tmp is somehow unwritable; set XDG_CACHE_HOME to a short path and try again.
Profiles live in your git config (default: ~/.gitconfig) under [identity "<name>"] sections. They follow git's normal [include] semantics — a profile in an included file is fully usable. git-id show <name> prints the source file.
That's working as intended: git-as only affects invocations that go through it. Plain git continues to use whatever the system agent offers first. If you want a specific repo to use a profile's identity automatically without typing git as, that's a separate feature (see "Just-works in this repo" wishlist, if/when it lands) — git-as is for explicit, per-command identity selection.
git-id— manage profiles (the configuration side)