pickey 0.4.0

Automatic SSH key selection for git
# pickey — Design Decisions

## The problem

You might need three different SSH keys for three different GitHub orgs. SSH has no concept of "use this key for this org".

The common workarounds all have a fatal flaw:

- **Host aliases** (`github.com-work` in `~/.ssh/config`) — contaminates your remote URLs with fake hostnames. Every clone URL must be rewritten. Breaks copy-paste, breaks scripts, breaks AI agents.
- **`includeIf "gitdir:"`** — ties identity to filesystem location. Breaks for `/tmp` clones, worktrees in unexpected paths, or any repo not under the expected directory.
- **`includeIf "hasconfig:remote.*.url:"`** — the remote doesn't exist yet during `git clone`, so the rule can't fire when it matters most.
- **SSH key managers** — indirection, does too much, or not built to work in tandem with git.

pickey solves this by sitting as git's `sshCommand`. At invocation time, the full remote URL is already in the SSH arguments — host, org, and repo. pickey matches that against rules and injects the right key. Works at clone time, works from `/tmp`, works when AI agents spawn terminals.

## Boundaries

pickey is a transparent SSH proxy for git. It does one thing: pick the right key.

**Does:**
- Select SSH key based on remote URL pattern
- Inject `-o IdentityAgent=none` (or the system agent on macOS) so only the selected key is offered
- Set repo-local `user.email`/`user.name` after SSH operations
- Block pushes when tracked unpushed commits have wrong author email
- Onboard from existing git config (`pickey init`)

**Does not:**
- Manage key lifecycle (create, rotate, delete) — use `ssh-keygen`
- Parse or modify `~/.ssh/config` — pickey's command-line flags (`-i`, `-o IdentitiesOnly=yes`) take precedence at runtime without touching SSH config
- Interact with ssh-agent — keys are read directly from disk via `-i`, agent is disabled per-invocation (except on macOS with Keychain enabled)
- Handle HTTPS auth — SSH only

## FAQ / Decisions

### Why `sshCommand`, not SSH config rewriting

Git's `core.sshCommand` receives the full remote URL in its arguments. SSH config only sees the hostname. By operating at the sshCommand level, pickey has access to the org and repo path — the exact information needed to pick the right key. No host aliases, no URL rewriting, no fake hostnames.

### Why spawn+wait, not exec

pickey needs to perform post-SSH actions: setting `user.email` and `user.name` in the repo's local git config after a successful operation. If pickey exec'd into ssh, it would lose control. Instead, it spawns ssh as a child, passes through stdin/stdout (git needs them for transport), waits for exit, performs post-actions, then exits with ssh's code.

### Why `IdentitiesOnly=yes` is always injected

Without it, ssh offers every key in the agent to the server, regardless of what `-i` specifies. On forges like GitHub, the server accepts the first key that matches *any* account — which may not be the one pickey selected. `IdentitiesOnly=yes` tells ssh to only offer the key pickey chose.

### Why `auto = true` exists

`pickey init` auto-detects rules from the user's existing SSH setup and writes them to config. But users also add their own rules manually. On re-run, init needs to update stale auto-detected rules without destroying manual ones. The `auto` field marks which rules init owns. Rules without `auto = true` are never touched by init.

### Why `git config --file` for conflict backup

When init finds conflicting `sshCommand` entries in includeIf configs or repo-local `.git/config`, it needs to disable them. Rather than text-manipulating config files, init uses `git config --file <path>` to read the current value, store it in `pickey.previousSshCommand` within the same file, and `--unset core.sshCommand`. Revert reads the backup and restores it. This uses git's own config system end-to-end — no parsing, no escaping bugs, clean round-trip.

### Why email/name in config?

Without pickey (or `includeIf`), it's easy to commit to a work repo with your personal email, or vice versa. If you set `email` and `name` on a rule, pickey writes them to the repo's local git config after each SSH operation — so the right identity is always used for commits, not just for SSH auth.

These fields are optional. If you only need SSH key routing, leave them out.

### Does pickey work with SSH over HTTPS (port 443)?

Yes. Because pickey sits on top of your system's SSH binary, any Hostname or Port overrides in your ~/.ssh/config are automatically respected. If you use explicit ssh://git@ssh.github.com:443 remotes, just use host = "ssh.github.com" in your pickey rules.

### Why macOS Keychain is enabled by default

On macOS, passphrase-protected SSH keys are common. Without Keychain integration, every `git push` or `git pull` would prompt for the passphrase — a dealbreaker for automated workflows and AI agent terminals.

pickey enables Keychain integration by default on macOS by:

1. Using Apple's OpenSSH (`/usr/bin/ssh`) instead of whatever `ssh` is on `$PATH` — only Apple's fork understands the `UseKeychain` option.
2. Injecting `-o UseKeychain=yes` so SSH reads saved passphrases from Keychain.
3. Injecting `-o AddKeysToAgent=yes` so the first successful passphrase entry is saved to Keychain automatically.
4. Injecting `-o IdentityAgent=<$SSH_AUTH_SOCK>` instead of `IdentityAgent=none` — the agent must stay connected for Keychain to supply passphrases. `IdentitiesOnly=yes` still prevents the agent from offering wrong keys.

The user experience: first `git push` with a passphrase key prompts once in the terminal, macOS saves the passphrase to Keychain, every subsequent operation is silent. No manual `ssh-add` needed.

Users who don't want this can set `[macos] use_keychain = false` in their config — pickey then falls back to `IdentityAgent=none` like on Linux.

On non-macOS platforms, the setting is ignored and `IdentityAgent=none` is always used.

#### A note on biometric prompts

Prior to macOS Sierra (10.12), SSH on macOS showed a GUI dialog offering to save the passphrase to Keychain. That dialog has been removed. There is no longer a biometric or GUI passphrase prompt for SSH on macOS — the passphrase is always entered in the terminal on first use, then stored and retrieved silently by Keychain on subsequent uses. See [Apple TN2449](https://developer.apple.com/library/archive/technotes/tn2449/_index.html).

If biometric authentication at the SSH protocol level is required (e.g. Touch ID per-connection), that requires a FIDO2 hardware-backed key (`ed25519-sk`). Such keys work with pickey without any special configuration — pickey injects `-i <key>` as usual and SSH handles the biometric challenge.