== @tool keychain: Manager for ssh-agent and private keys, with native GnuPG support.

Keychain orchestrates `ssh-agent` and gives you one coordinated, long-running SSH agent per user and host. It is a frontend to `ssh-agent` and `ssh-add` that lets you keep one long-running ssh-agent process per user per host -- rather than the default of one per login session -- so you only need to enter each key's passphrase once per boot.

*The Keychain difference:*

```
┌─────────────────────────────────────────────────────────────┐
│  Without Keychain:                                          │
│                                                             │
│  Terminal 1 → ssh-agent (prompt)                            │
│  Terminal 2 → ssh-agent (prompt again)                      │
│  Terminal 3 → ssh-agent (prompt again)                      │
│  Cron job → no agent (fails)                                │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  With Keychain:                                             │
│                                                             │
│  Terminal 1 ─┐                                              │
│  Terminal 2 ─┼→ single ssh-agent (prompt once)              │
│  Terminal 3 ─┤    ↓                                         │
│  Cron job ───┘    all terminals reconnect automatically     │
└─────────────────────────────────────────────────────────────┘
```

This dramatically reduces the number of times you need to enter your passphrase. With `keychain`, you only need to enter a passphrase once every time your local machine is rebooted. Keychain also makes it easy for remote cron jobs to securely "hook in" to a long running `ssh-agent` process, allowing your scripts to take advantage of key-based logins.

Keychain also supports the ability to auto-warm GnuPG signing and encryption keys so they are ready for use, and allows you to easily wipe cached GnuPG secrets using the `keychain wipe --gpg` command. GnuPG features require *GnuPG 2.1 or later*. When a GPG key is requested, Keychain asks GnuPG to prove the requested signing or decryption capability so any required passphrase is cached in memory. While Keychain manages the lifecycle of `ssh-agent` for you, GnuPG retains complete ownership of `gpg-agent` startup, configuration, and lifecycle.

*Keychain 3.0.0_beta2+* introduces coordinated multi-terminal initialization, eliminating the `could not acquire lock` errors that occurred when multiple shells started simultaneously. When Visual Studio Code reconnects to WSL and restores several terminals at once, all terminals now coordinate automatically -- press Enter in any one terminal to initialize keys, and all others are notified when complete. See `keychain man topic:coordination`.

== @topic requirements: Keychain 3, Requirements and Backward Compatibility [category=intro, priority=40]

Keychain supports most UNIX-like operating systems and intentionally limits its scope to modern, widely deployed implementations of OpenSSH and GnuPG.

*From shell script to Python zipapp.* Keychain was conceived as a single POSIX Bourne shell script that grew steadily more sophisticated since its inception in 2002. In 2026, Keychain 3 moved to a new, portable technology: a single Python *zipapp* -- a single executable `.pyz` file that runs on any system with Python 3.9 or newer, without requiring external Python dependencies. This provides a modern runtime while maintaining the self-contained, script-based nature of Keychain.

*Action-driven interface.* Keychain 3 introduces a new action-driven interface (`keychain add id_rsa`, `keychain agent stop --mine`, `keychain wipe --ssh`, etc.) and is now the recommended way to drive Keychain. A full embedded documentation engine provides help via `--help <action>` or `--explain` for context-sensitive documentation. The complete man page is available via `keychain man`, with `keychain man --list` showing all available sections.

*Backward compatibility.* Maintaining keychain 2.x CLI compatibility was a primary goal. The legacy flat-flag command line (for example `keychain --eval --quiet id_rsa`) continues to work exactly as before via an explicit compatibility shim.

*Shell support.* Keychain 3 continues to be compatible with Bourne `sh`, `csh`/`tcsh` and `fish` shells. `bash`, `ksh` and `zsh` are compatible with the Bourne `sh` built-in to Keychain. Keychain also supports integration with *systemd*.

== @topic usage: General Usage [category=intro, priority=45]

Typically, you configure Keychain to run from your shell startup files. Use the shell-specific recipes in `keychain man topic:env` so interactive terminals can load keys while non-interactive login shells establish the agent environment without prompting. To load a key from an interactive Bourne-compatible shell, run:

```
eval "$(keychain add id_rsa --eval)"
```

Keychain will start `ssh-agent` if one isn't already running, then checks to make sure your private keys (in this example, *id_rsa*) are loaded into the agent. If they are not, you are prompted for any passphrase necessary to decrypt them, so that they are cached in memory and available for use.

In addition to printing user-friendly output to your terminal, keychain outputs important `ssh-agent` environment variables. Command substitution captures this output, and the shell built-in `eval` evaluates it, causing these variables to persist in your current session.

These ssh-agent environment variables are also written to `~/.keychain/${HOSTNAME}-sh`, so that subsequent logins and non-interactive shells such as cron jobs can source this file to access the running `ssh-agent` and make passwordless ssh connections using the cached private keys -- even when you are logged out. These files are collectively called *pidfiles*.

*Multi-terminal coordination (3.0.0_beta2+).* When multiple shells start simultaneously, all terminals coordinate automatically. Each displays `Press Enter to run ssh-add in this terminal`. Pressing Enter in any one terminal runs `ssh-add`; other terminals wait and are notified when complete. This eliminates the `could not acquire lock` errors from earlier versions. See `keychain man topic:coordination`.

The key files specified on the command-line will be searched for in the *~/.ssh/* directory, and keychain will expect to find the private key file with the same name, as well as a *.pub* public key. Keychain will also see if any GnuPG keys are specified and, if so, ask GnuPG to exercise the requested operation so any required passphrase is prompted for and cached.

Typically, private SSH key files are specified by filename only, without path, although it is possible to specify an absolute or relative path to the private key file as well. Private key files can be symlinks to the actual key. PKCS#11-backed SSH tokens can be requested explicitly via `pkcs11:/path/to/provider.so`; Keychain will compare the token's exported public keys against the current agent and, when needed, load the provider via `ssh-add -s`.

For GPG keys, Keychain exercises the requested capability so `gpg-agent` has the required passphrase cached and ready for use. Use `gpgs:KEYID` for signing-oriented workflows, `gpge:KEYID` for decrypt-oriented workflows such as `pass`, and `gpga:KEYID` when both capabilities should be warmed. The `gpge:` prefix performs an explicit encrypt-then-decrypt verification cycle -- a tiny temporary payload is encrypted to the key and immediately decrypted through `gpg-agent`. If this verification cannot be completed, `add` fails rather than reporting success. This is more reliable than relying on signing warm-up side effects, which may not populate the decryption passphrase cache on all GnuPG versions and key configurations. The legacy `gpgk:KEYID` alias is equivalent to `gpgs:KEYID` (signing warm-up only).

== @action add: Add keys to the agent (auto-start one if needed).

@syntax keychain add [OPTIONS] [KEYS...]

`add` is the workhorse of Keychain 3 -- in Keychain 2.x, this was the implied action when no other action was specified. It tells Keychain to first ensure that an `ssh-agent` is currently running (equivalent to an automatic `keychain agent start`). If not, one will be started. Then, for each KEY argument:

* For each SSH key, do nothing if `ssh-agent` already holds it; otherwise, prompt for its passphrase and load it.
* For each GPG key, ask GnuPG to prove the requested signing or decryption capability. GnuPG owns any `gpg-agent` and pinentry interaction.
* Write the SSH agent's environment to a per-user, per-host *pidfile* under `~/.keychain/` that scripts can `source` to gain access to the agent.

*Coordinated multi-terminal initialization (3.0.0_beta2+).* When multiple shells start simultaneously, Keychain 3.0.0_beta2+ uses a coordinated unlock protocol instead of the classic lock-timeout approach:

1. All terminals that detect missing SSH keys display: `Press Enter to run ssh-add in this terminal`.
2. Pressing Enter in *any* terminal designates it to run `ssh-add`.
3. Other terminals wait automatically and are notified when initialization completes.
4. When notified, waiting terminals print `Keys initialized by another terminal.` and configure their environment without prompting.

*Moving a passphrase request.* If the terminal that started `ssh-add` becomes inaccessible, press Enter at Keychain's waiting prompt in your visible terminal. Keychain cancels the existing request before prompting there and prints a short notice. The original terminal waits for the replacement operation to finish.

*Transitioning from classic lock behavior.* In earlier versions, simultaneous terminal startups would race for a lock, with losers timing out after 5 seconds (or the `--lockwait` value). The new coordination model is fundamentally different: terminals cooperate rather than compete, and all terminals succeed together once any one completes the passphrase entry.

Bare key names are looked up under *~/.ssh/* and as GPG key IDs; absolute paths are loaded directly. Symbolic links are supported. Extended-key syntax -- `sshk:PATH`, `gpgk:KEYID`, `gpgs:KEYID`, `gpge:KEYID`, `gpga:KEYID`, `pkcs11:PATH`, `host:HOSTNAME` -- is automatically detected and can be used to explicitly specify the lookup method.

When one or more KEY arguments are supplied and *none* of them resolve to a real SSH key or known GPG identity, `add` exits before starting or attaching to an `ssh-agent`. This is an intentional compatibility accommodation: keychain 2.x bare-token invocation is still accepted, but a mistyped modern action should not accidentally spawn a fresh SSH agent as a side effect. GnuPG may perform its normal `gpg-agent` autostart while resolving a bare token against local GPG key material. A token unknown to the local GPG keyring does not normally trigger agent autostart.

*Note:* The coordinated unlock flow applies to SSH key loading only. Due to differences in how GPG operates, GPG keys use Keychain's explicit GnuPG warm-up paths (`gpgs:`, `gpge:`, `gpga:`) and do not participate in the multi-terminal coordination protocol.

See also: `topic:extkeys`, `topic:coordination`.

== @action agent: Agent lifecycle (start | stop).

@syntax keychain agent <start|stop> [OPTIONS]

Domain command for managing agent processes without loading keys. `agent start` ensures an agent exists and can emit its environment; `agent stop` terminates agents recorded in the pidfile.

== @action agent start: fork an agent if none is running (no key load)

@syntax keychain agent start [OPTIONS]

Start an `ssh-agent` if none is running, then optionally emit its environment for the current shell. By default, Keychain pins a spawned `ssh-agent` to a host-specific socket path under `~/.keychain/` so the agent endpoint stays stable across WSL and other early startup environments that may clean `/tmp`. This action does not load any keys; use `add` when you want prompt-and-load semantics.

== @action agent stop: terminate ssh-agents (default: all in pidfile)

@syntax keychain agent stop [--mine|--others]

Terminate the ssh-agents recorded in the pidfile. Bare `agent stop` targets every recorded agent; `--mine` and `--others` narrow the kill list by owner UID.

== @action list: List keys currently held by ssh-agent.

@syntax keychain list [--json]

Print the type, bit-length, fingerprint, and comment of every key held by the reachable ssh-agent, in the same shape as `ssh-add -l`. When the modern color theme is active, the output is rendered as a clean four-column table.

`--json` emits a JSON array suitable for scripting.

== @action wipe: Clear SSH identities by default, or explicitly clear the gpg-agent cache.

@syntax keychain wipe [--ssh] [--gpg]

Clear cached credentials while leaving the agent processes running. By default, `wipe` removes identities from `ssh-agent` only (this behavior is new as of version 3.0.1 -- previous versions wiped GnuPG keys as well). GnuPG is affected only when `--gpg` is specified. Keychain then connects to an already-running `gpg-agent` and sends `RELOADAGENT`. GnuPG flushes the agent's entire in-memory secret cache, not just passphrases warmed by Keychain. This makes protected signing and decryption capabilities cold again and may clear other cache modes maintained by the same agent. No key files or other persistent key material are deleted.

    wipe              SSH identities only
    wipe --ssh        SSH identities only
    wipe --gpg        gpg-agent's in-memory secret cache only
    wipe --ssh --gpg  both

As part of `RELOADAGENT`, GnuPG also rereads its agent configuration and trust list. Clearing an absent `gpg-agent` is a successful no-op. If `gpg-connect-agent` is unavailable, times out, or cannot confirm the cache flush, Keychain reports an error.

== @action forget: Evict specific SSH keys from ssh-agent.

@syntax keychain forget [KEYS...]

For each KEY argument, evict the matching SSH key from `ssh-agent`. Bare key names are looked up under *~/.ssh/*; absolute paths are used directly. Extended-key syntax supports `sshk:PATH` and `host:HOSTNAME`. `gpgk:KEYID` and `pkcs11:PATH` are not supported by `forget`.

Without arguments, `forget` is a no-op (use `wipe` to remove all keys).

== @action env: Print agent env in the requested format.

@syntax keychain env [--shell FORMAT] [--json]

Print `SSH_AUTH_SOCK` and `SSH_AGENT_PID` from the best available source (running pidfile, then inherited env) in the requested format. Output targets:

    env       bare `KEY=value` lines (default)
    sh        Bourne shell `KEY=value; export KEY`
    csh       C-shell `setenv KEY value`
    fish      `set -x KEY value`
    json      JSON object on one line
    eval      same as `add --eval`: shell-detected dialect
    systemd   bare KEY=value (suitable for EnvironmentFile=)

`--target` is an alias for `--shell`; `--json` is shorthand for `--shell json`.

== @action inspect: Structured snapshot of every state probe.

@syntax keychain inspect [KEYS...] [--json]

Run every state probe keychain would run during a real `add` (host detection, paths, perms, env-file, agent reachability, key list, extkey expansion, ...) and present the internal runtime in easy-to-read topical boxes. Runtime identity, the parsed `~/.keychainrc`, effective preferences and their sources, and the presence of Keychain-relevant environment variables are included. Environment reporting is restricted to a diagnostic allowlist; unrelated variables and literal configuration-file contents are never exposed. Use with `--json` to generate a complete machine-readable state dump suitable for bug reports. The versioned JSON schema groups diagnostics under `runtime`, `configuration`, `keychain_state`, `agent_state`, and `keys`.

== @action version: Print version banner.

@syntax keychain version [--json]

Print the version string, copyright, and license summary.

== @action help: Print the cheat-sheet (same as --help).

@syntax keychain help [<action>]

Print the top-level cheat sheet. With an action name, print the per-action cheat sheet (same as `keychain <action> --help`). Identical to `keychain --help`.

For full documentation, use `keychain man`, `keychain man --list` for a man index, and `keychain man <doc-atom>` for specific documentation on an action, option or topic. To display all documentation relevant to your current command-line, append `--explain`.

== @action man: Full documentation.

@syntax keychain man [<action>|<topic>] [--list] [--no-pager] [--width N]

Print the full embedded documentation in a pager-friendly form. Targets:

    keychain man                  whole Keychain manual
    keychain man <action>         one action (incl. all flags)
    keychain man <topic>          named topical section
    keychain man topic:<name>     same, with explicit prefix
    keychain man --list           index every doc atom
    keychain man --no-pager       skip $PAGER even on TTY
    keychain man --width 100      explicit wrap width

On a terminal, Keychain uses `$PAGER`, then `less`, then `more`. Color is retained only when the selected executable is verified as `less`, including aliases such as macOS `more`; when `$LESS` is unset or empty, Keychain supplies `LESS=-R` to that child process without changing the caller's environment. Other pagers receive plain text.

== @option eval: emit shell env for `eval $(keychain ...)`

@syntax keychain add --eval [KEYS...]

This is a foundational keychain feature. If you're using Keychain on a POSIX system, you'll likely want to use this flag in your shell startup files (e.g. *~/.bash_profile*) to ensure that every new shell session can find and use `ssh-agent` without manual intervention.

Here's how it works. When you pass `--eval`, Keychain prints shell commands that set the environment variables needed by `ssh` and `scp` to find the running `ssh-agent`. Your shell's built-in *eval* executes those commands in the current shell. Keychain emits the appropriate environment syntax for the selected shell, but the surrounding command substitution must also use that shell's syntax. From an interactive Bash or Zsh terminal, run:

```
eval "$(keychain add --eval id_ed25519)"
```

The shell runs Keychain before `eval` processes its output, so `--eval` does not prevent a key-loading prompt. For automatic startup, use the guarded recipes in `keychain man topic:env`.

If you just want to retrieve the agent environment without adding any keys, use the `keychain env --target eval` action instead, or source the appropriate pidfile.

== @option systemd: push agent env to `systemctl --user`

After loading keys, push `SSH_AUTH_SOCK` and `SSH_AGENT_PID` into the calling user's systemd user environment block using `systemctl --user set-environment`. This makes the agent environment available to all child systemd units (services, timers) without requiring them to source a pidfile.

Use this option when your user session is managed by systemd and you need background services to access the SSH agent. From an interactive Bash or Zsh terminal, run:

```
eval "$(keychain add --eval --systemd id_ed25519)"
```

For automatic startup, add `--systemd` to both Keychain invocations in the Bash or fish recipe in `keychain man topic:env`. Keep `--noask` in the non-interactive branch so publishing the agent environment does not prompt for keys.

The `--systemd` flag is a side-effecting operation that runs `systemctl` directly; it does not print any shell code. If you only need to update the systemd environment without loading keys, use `keychain env --target systemd` instead, which prints the agent environment in systemd `EnvironmentFile=` format (bare `KEY=value` lines).

== @option quick: short-circuit if an agent already has any key

If a reachable ssh-agent already holds at least one key, exit immediately without prompting or loading anything new. Intended for shell-startup snippets where the agent is reused across many shells: the shell pays only the cost of a single `ssh-add -l` on subsequent invocations.

This is intentionally an SSH-only compatibility option. GPG key arguments are ignored and Keychain does not invoke GnuPG when `--quick` is present. Use normal `keychain add` for GPG workflows.

== @option immediate: begin coordinated SSH key activation without waiting for Enter

When requested SSH keys are missing, start `ssh-add` without displaying `Press Enter to run ssh-add in this terminal`. Immediate invocations can prompt independently, so a hidden terminal's passphrase request does not prevent a visible terminal from loading keys. After another terminal completes loading, each invocation checks its own requested keys and cancels its unfinished `ssh-add` if those keys are now available.

This option does not suppress an `ssh-add` passphrase prompt, hide errors, disable coordination, or imply `--quiet`. For quiet automatic activation, add `--quiet --immediate` to the interactive branch of your startup recipe (see `keychain man topic:env`). The corresponding command in an interactive Bash or Zsh terminal is:

```
eval "$(keychain add --eval --quiet --immediate id_ed25519)"
```

A failed invocation does not cancel other attempts. Terminals requesting different keys can continue independently. Pressing Enter in a regular waiting terminal moves outstanding requests there; canceled invocations wait for that operation instead of immediately starting another prompt. Keychain reports the confirmation and lifetime settings used by a successful load. It does not reconcile differing settings: if the same identity is added again, OpenSSH applies that addition's settings. To make automatic activation persistent:

```
[agent]
activation = immediate
```

The `--immediate` flag overrides the configured activation mode for this invocation. See `keychain man config:agent.activation` for package defaults and the legacy `immediate` config key.

== @config agent.activation: choose prompted or immediate SSH key activation

Set `[agent] activation = prompt` to wait for Enter before attempting coordinated SSH key initialization, or `activation = immediate` to begin automatically. This controls the initial activation step, not passphrase entry: `ssh-add` may still prompt, and immediate terminals may wait for another terminal to finish initialization.

Upstream builds default to `prompt`. A package may be built with `immediate` as its default; both behaviors remain available. Your `~/.keychainrc` overrides the package default, and `--immediate` overrides the config for one invocation. To restore the Enter prompt on a package that defaults to immediate activation:

```
[agent]
activation = prompt
```

The legacy `[agent] immediate = true` and `immediate = false` settings remain accepted and mean `activation = immediate` and `activation = prompt`, respectively. If both keys are present, `activation` wins regardless of their order, and Keychain warns that `immediate` was ignored. Invalid activation values are errors. See `keychain man --immediate` for coordination and failure behavior.

== @option no-passphrase: set up ssh-agent without loading keys

Complete SSH-agent setup and any requested environment output, then exit without inspecting, loading, or warming the specified keys. Keychain does not invoke GnuPG in this mode. The command can therefore succeed even when a requested key is not available; this option does not verify key availability.

For a modern agent-only invocation, prefer `keychain agent start`. The `--noask` spelling and its `--no-passphrase` alias preserve the traditional Keychain 2.x behavior.

== @option confirm: ssh-add -c: prompt per use

Pass `-c` to `ssh-add`: every subsequent use of the loaded key (for example, by `ssh`, `scp`, or Git over SSH) requires explicit user confirmation through OpenSSH's askpass mechanism. This is normally a persistent preference:

```
[agent]
confirm = true
```

On macOS, `--confirm` implicitly applies `--no-inherit`: Keychain does not adopt the `$SSH_AUTH_SOCK` / `$SSH_AGENT_PID` inherited from the parent shell. This ensures that, when no valid Keychain pidfile agent exists, Keychain starts an agent with the native confirmation environment described below. Other platforms retain normal inherited-agent discovery.

*macOS.* When Keychain starts a new managed `ssh-agent` with confirmation enabled, it installs a private helper at `~/.keychain/ssh-askpass-macos` (or the selected Keychain directory) and starts the agent with the environment OpenSSH needs to invoke it. The helper accepts confirmation requests only and displays a native graphical *Keychain SSH Confirmation* dialog. Only choosing *Allow* approves the operation; choosing *Deny*, closing or cancelling the dialog, an unavailable desktop session, or any helper failure refuses it.

If `$SSH_ASKPASS` already names a custom helper when Keychain starts the managed agent, Keychain preserves it and enables OpenSSH's forced askpass mode. On other platforms, provide a suitable askpass helper in the agent's startup environment when the platform does not supply one.

An agent's askpass environment is fixed when the agent starts, and a valid pidfile agent is considered before inherited-agent discovery. After upgrading, run `keychain agent stop` once if Keychain's pidfile refers to an agent created without confirmation support; the next confirmed invocation will start it with the native helper. See issue #227.

`--confirm` cannot be combined with `--no-gui`. Keychain reports an error instead of silently weakening the request or loading a key whose confirmation constraint cannot be satisfied.

== @option timeout: auto-expire keys after MIN minutes

Pass a timeout through to `ssh-add`: the key is forgotten by the agent after the requested number of minutes. Common settings include 480 (8h, a workday) or 60 (higher security). A common config-file preference.

== @option ignore-missing: skip keys that don't exist

For each KEY positional that doesn't resolve to an existing file (or known GPG ID), skip it silently instead of erroring. Useful in dotfiles shared across machines where not every key is present everywhere. If *all* requested keys are missing, `add` still exits successfully without spawning an agent.

== @option clear: wipe SSH identities before adding

Remove every identity from the selected `ssh-agent`, then process the requested keys normally. This is equivalent to calling:

```
keychain wipe && keychain add KEYS
```

`--clear` does not flush GnuPG's passphrase cache, even when GPG keys are also requested. Use `keychain wipe --gpg` when that separate operation is intended.

== @option confallhosts: load every IdentityFile in ~/.ssh/config

Iterate every `Host` block in *~/.ssh/config* (excluding pure-wildcard patterns and negations) and load its `IdentityFile`. Equivalent to `--extended host:NAME` for every host. Per-host expansion is delegated to `ssh -G NAME` so include files, Match blocks, and `%d`/`%u`/`%h` tokens work exactly as OpenSSH does.

== @option ssh-allow-forwarded: adopt a forwarded SSH agent

Adopt the inherited `$SSH_AUTH_SOCK` if it points to a forwarded agent (typical of `ssh -A` from a workstation into a server). *Has security implications*: the forwarded socket is controllable by the source machine. See `keychain man topic:security`.

== @option no-inherit: ignore inherited SSH_AUTH_SOCK / PID

Don't trust `$SSH_AUTH_SOCK` / `$SSH_AGENT_PID` from the parent environment; always start fresh from the pidfile or by spawning. The inverse of `--ssh-allow-forwarded`.

== @option ssh-agent-socket: use PATH as the ssh-agent endpoint

Use PATH as the ssh-agent socket explicitly. When this option is not supplied, Keychain now chooses a host-specific managed socket path under `~/.keychain/` instead of letting `ssh-agent` pick a transient location under `/tmp`. Override it only when integrating with another tool that expects a specific socket path.

== @option mine: terminate only this UID's agents

Restrict the kill list to agents whose owner UID matches the calling user. Useful in shared accounts or root-cron contexts where you must not touch other users' agents.

== @option others: terminate agents owned by others

Inverse of `--mine`: terminate only agents whose owner UID does *not* match the calling user. Rare; typically a maintenance tool.

== @option all: terminate all candidate agents

Terminate every candidate agent recorded in the pidfile, regardless of owner. This is the default target for `keychain agent stop`.

== @option list-json: emit machine-readable JSON on stdout

Emit a JSON array of key records on stdout.

== @option wipe-ssh: remove SSH-agent identities only

Remove all identities held by `ssh-agent` using `ssh-add -D`. This does not delete the corresponding private key files.

== @option wipe-gpg: flush the existing gpg-agent's entire in-memory secret cache

Send `RELOADAGENT` to an already-running `gpg-agent`. GnuPG flushes the agent's entire in-memory secret cache, not only entries warmed by Keychain, and rereads its configuration. Protected signing and decryption operations will need to unlock their keys again. No private keys or other persistent key material are deleted, and the agent process remains running.

== @option env-shell: output target (default: env)

Specify the output format for `keychain env`. Supported targets:

* `env` -- bare `KEY=value` lines (default, suitable for `EnvironmentFile=`)
* `sh` -- Bourne shell `KEY=value; export KEY`
* `csh` -- C-shell `setenv KEY value`
* `fish` -- fish shell `set -x KEY value`
* `json` -- JSON object on one line
* `eval` -- shell-detected dialect (same as `add --eval`)
* `systemd` -- bare `KEY=value` (alias for `env`, suitable for systemd)

Use `--target systemd` or `--shell systemd` when you want to redirect output to a file for use with systemd's `EnvironmentFile=` directive. The `--systemd` flag on `add` performs the same function but pushes directly to systemd via `systemctl --user set-environment` instead of printing to stdout.

== @option env-json: shorthand for --shell json

Equivalent to `--shell json`.

== @option inspect-json: emit JSON

Emit the inspection payload as JSON.

== @option version-json: emit JSON

Emit `{"version": "X.Y.Z"}`.

== @option man-list: list every doc target with its one-liner

Print `slug  --  one-liner` for every action, global flag, config key, and topic. Useful as a discovery surface.

== @option man-no-pager: don't pipe through $PAGER on TTY

Skip the automatic `$PAGER` invocation even when stdout is a terminal.

== @option man-width: wrap output at WIDTH columns

Explicit wrap width. Defaults to `$COLUMNS` then 80.

== @global help: print help

Print the top-level help cheat sheet, or action-specific help when followed by an action path. `-h` is an alias.

== @global version: print version

Print the version banner and exit. `-V` is an alias.

== @global quiet: suppress non-error output on stderr

Silence routine Keychain messages and successful `ssh-add` reports. Required interactive prompts, explicit results from actions such as `list`, warnings, and errors are still printed.

== @global debug: trace agent-selection decisions on stderr

Emit extra details about agent-discovery decision keychain makes: which pidfile candidates were considered, which sockets were probed, why one was selected, what `ssh -G` returned for each `host:` extkey, etc.

== @global debug-log: append diagnostics to a private file

Use `--debug-log FILE` to collect Keychain diagnostics during shell or graphical-login startup without redirecting terminal output. The file receives diagnostic details even without `--debug`, including routine messages hidden by `--quiet`. Normal console output and the shell assignments produced by `--eval` are unchanged.

Records are plain text and include a UTC timestamp, process ID, and terminal name (`none` when unavailable). Existing records are preserved. The parent directory must already exist; the log must be a regular file owned by you, not a symlink or hard link. Keychain creates the file with private permissions and restricts an existing file to owner read/write access.

```
eval "$(keychain add --eval --debug-log ~/.keychain/debug.log id_ed25519)"
```

For shell startup, add `--debug-log FILE` to both branches of the Bash or fish recipe in `keychain man topic:env`, retaining `--noask` in the non-interactive branch.

The log records Keychain's decisions, not terminal input, passphrases, or private-key contents. It does not capture prompts that `ssh-add` writes directly to its terminal. Review paths and configuration details before sharing a log in a bug report. Remove the option when troubleshooting is complete; Keychain does not rotate the log.

== @global nocolor: disable ANSI color output

Disable ANSI color output. Useful when piping to a tool that doesn't strip escape codes, or in log capture where color bytes muddy the file. The conventional `$NO_COLOR` environment variable (https://no-color.org) is also honored; the flag wins when both are set. `--no-color` is an accepted alias.

== @global theme: color theme: legacy or modern

Select the color / glyph theme. `legacy` is the keychain 2.x output style; `modern` is the 3.0 default. A once-forever lifestyle preference; usually set in `~/.keychainrc`.

== @config output.animate: animate the initial activation prompt

The default is `true`. On a suitable color terminal, the complete activation prompt appears immediately, then changes from subdued text to its final colors from left to right over about half a second. This happens once; it does not repeat while Keychain waits. Enter works throughout, and the animation does not delay coordination events.

Set `animate = false` under `[output]` in `~/.keychainrc` for a static prompt with the same final colors. There is no corresponding command-line option. Disabled color, redirected output, an unsuitable terminal, or insufficient or unknown terminal width also produce a static prompt. Required prompts remain visible with `--quiet`.

== @global no-gui: don't use GUI passphrase prompts

Don't use GUI passphrase prompts (DISPLAY / SSH_ASKPASS). Force a TTY prompt instead. A once-forever lifestyle preference for headless or screen-shared environments.

== @global gpg2: force the gpg2 binary

Force the `gpg2` binary instead of `gpg`. Kept for 2.x compatibility; modern systems normally provide GnuPG 2 as the default `gpg`.

== @global absolute: treat --dir as the literal pidfile dir

Treat `--dir` (or `[paths] dir`) as the literal pidfile directory; don't append `/.keychain`.

== @global allow-env: allow KEYCHAIN_* environment variables

By default, Keychain silently ignores all `KEYCHAIN_*` environment variables to prevent accidental or malicious injection in multi-user environments. Pass `-E` or `--allow-env` to allow `$KEYCHAIN_CONFIG` to override the config file path and `$KEYCHAIN_SSH_AGENT_ARGS` to inject extra flags into a spawned `ssh-agent`.

This flag is intended for trusted, single-user environments or CI pipelines where the shell environment is controlled. It is *not* needed for normal interactive use; all KEYCHAIN_* behavior can be achieved via `~/.keychainrc` configuration.

== @global dir: pidfile directory (default: ~/.keychain)

Override the per-user state directory. By default keychain uses `~/.keychain/` for pidfiles, env-files, and lock files. `--absolute` treats the value verbatim instead of appending `/.keychain`.

== @global host: override hostname used for pidfile naming

`--host HOSTNAME` (or the more explicit `--hostname HOSTNAME`) overrides the hostname used in pidfile names. Pidfiles are named `HOST-sh` / `HOST-csh` / `HOST-fish` etc. so multiple hosts sharing a homedir over NFS each get their own state. By default keychain probes `hostname(1)`, `$HOSTNAME`, then `uname -n`.

== @config agent.env.ssh_args: extra args passed to spawned `ssh-agent`

Append extra arguments when keychain spawns `ssh-agent`. This is an advanced escape hatch for cases Keychain does not model directly, such as pinning the agent socket to a specific path or enabling agent-level debugging. The value is split into shell-like tokens and appended after Keychain's own managed flags. For example, this pins the `ssh-agent` socket to a fixed location:

```
[agent.env]
ssh_args = -a ~/.ssh/agent.sock
```

If `$KEYCHAIN_SSH_AGENT_ARGS` is set in the environment and `--allow-env` is active, it overrides this config key.

== @config paths.pid_formats: pidfile output formats

Comma-separated pidfile formats to write after agent setup. The default is `sh`; supported values are `sh`, `csh`, `fish` and `envfile`.

== @global lockwait: max seconds to wait for the state lock

Maximum time (in seconds) to wait for keychain's state lock when another keychain process is mutating shared coordination state. Default 5 seconds.

*Historical note:* In Keychain 2.x and 3.0.0_beta1, `--lockwait` controlled the timeout for the single global lock that protected the entire key-loading operation. This was the source of the `could not acquire lock` errors when multiple terminals started simultaneously. Starting in 3.0.0_beta2, the lock scope is much narrower (protecting only small coordination state updates) and the interactive unlock flow uses a separate activation lock with FIFO-based notification. As a result, `--lockwait` is now an internal tuning parameter that most users will never need to adjust. Set to 0 to fail immediately if the state lock is held (rare in normal operation).

== @global no-lock: skip the state lock (not recommended for interactive use)

Skip the cooperative state lock entirely. *Warning:* This disables the coordination protocol that prevents multiple terminals from racing to run `ssh-add` simultaneously. Only use this in controlled environments where simultaneous interactive key loading is impossible by construction, such as:

* CI runners that serialize externally
* Cron jobs with guaranteed non-overlapping execution
* Single-terminal, non-interactive scripts

For normal interactive use -- especially in environments like Visual Studio Code + WSL where multiple terminals may start at once -- leaving the lock enabled is essential for proper coordination. The lock overhead is now minimal (milliseconds) since it protects only small state updates, not the entire passphrase prompt duration. `--nolock` is the legacy spelling.

== @global explain: explain this invocation instead of running it

This global instructs keychain to not actually act upon the the command-line but instead evaluate the command-line arguments (minus the `--explain` option) and print documentation boxes for the action, every recognised option and positional argument to standard output. Essentially, this tells Keychain to explain the command invocation it would have performed to you, by printing all relevant documentation snippets.

== @topic coordination: Coordinated key initialization (3.0.0_beta2+) [category=intro, priority=50]

*The problem.* When multiple terminal shells start simultaneously, each shell may run `keychain add --eval` to initialize SSH keys. In Keychain 2.x and 3.0.0_beta1 and earlier, all terminals would race to acquire a single lock. One terminal would win and prompt for the passphrase; all others would wait for the lock timeout (default 5 seconds) and then fail with `could not acquire lock`. Users had to manually delete the lockfile or retry.

This was especially problematic in Visual Studio Code: only one terminal is visible at a time, so the terminal running `ssh-add` might be hidden behind other terminal tabs. Users had to click through each terminal tab to find the one prompting for the passphrase, turning what should be a simple workflow into a frustrating scavenger hunt.

*The solution (3.0.0_beta2+).* Keychain now uses a coordinated unlock protocol designed specifically for multi-terminal scenarios:

1. *State lock (short-lived).* A brief lock protects agent setup and coordination updates, including waiter registration. Agent queries and passphrase entry run outside this lock, so a slow or unanswered prompt does not block those updates.

2. *Loading and clearing.* Each loading attempt has its own lock. Loading attempts share the activation lock, while SSH clearing requires exclusive access. This permits independent immediate prompts without allowing `wipe` to clear the agent during loading.

3. *FIFO notification.* Waiting terminals create private named pipes (FIFOs) and sleep until keyboard input, a notification, or the end of a loading operation wakes them. A loading terminal also listens for cancellation and other terminals' results; it checks the agent before canceling its own redundant prompt.

4. *Moving the request.* Pressing Enter in a waiting terminal asks existing loaders to cancel their requests. Keychain waits for those loading children to exit before starting a replacement and displays a short notice. The canceled terminals wait for the replacement operation.

*User experience.* All terminals that detect missing SSH keys display:

```
 ▸ Press Enter to run ssh-add in this terminal
```

Pressing Enter in any one terminal starts `ssh-add`. Other terminals show:

```
 ▸ Press Enter to move the passphrase request to this terminal
```

When initialization completes, waiting terminals wake automatically and print:

```
Keys initialized by another terminal.
```

*Automatic activation.* `--immediate` skips Keychain's initial Enter prompt. Several terminals can run `ssh-add` at the same time, but you only need to complete a prompt that loads your requested keys. Other invocations check the actual agent after completion notifications and cancel their own redundant prompts. A success message for another terminal's different key request is not enough: each invocation checks its own keys. Failed attempts do not cancel useful prompts elsewhere. The normal Enter prompt remains the default so users can choose where passphrase entry begins.

*Implementation details.* The latest completion record is stored in `~/.keychain/<host>.state.json`; it is not proof that an operation is alive or that keys are loaded. Per-attempt locks, lifetime and cancellation FIFOs, and notification FIFOs live in `~/.keychain/<host>-waiters/`. The shared loading/exclusive clearing lock is `~/.keychain/<host>.activation.lock`. Runtime files are private to the current user and are checked before use.

*Scope.* The coordinated unlock flow applies to SSH key loading only. GPG keys use explicit warm-up paths (`gpgs:`, `gpge:`, `gpga:`) and do not participate in multi-terminal coordination.

*Legacy behavior.* Users upgrading from Keychain 2.x or 3.0.0_beta1 will notice the absence of `Waiting N seconds for lock...` messages during interactive key loading. This is intentional: internal coordination is quiet, and the state lock is held only briefly for metadata updates.

== @topic security: Security model [category=appendix, priority=50]

Keychain runs with the authority of the invoking user. It assumes the operating system, the current user account, the user's executable search path, OpenSSH, and GnuPG are trusted. Within that boundary, Keychain protects its runtime state from other local users, validates agent endpoints before use, coordinates concurrent invocations, and quotes generated shell output.

*Runtime storage.* The default `~/.keychain/` directory is created mode `0700`. Before runtime state is used on POSIX systems, Keychain checks the directory and existing pidfiles, state files, lock files, and waiter directory. It refuses paths owned by another user, paths with any group or other permissions, and paths that cannot be inspected. Pidfiles and coordination state are replaced through private temporary files.

*Agent endpoints.* An SSH endpoint must be a non-symlink UNIX-domain socket owned by the current user. Pidfile agents must also name a live process, and the selected endpoint is checked again immediately before `ssh-add` runs. Forwarded agents require the explicit `--ssh-allow-forwarded` opt-in. A socket identified as GnuPG's SSH endpoint is always rejected; Keychain uses `ssh-agent` exclusively for SSH identities.

*Configuration and environment.* On POSIX systems, `.keychainrc` must be owned by the current user and must not be writable by group or others. `KEYCHAIN_*` environment variables are ignored unless `--allow-env` (`-E`) is supplied. External programs receive argument vectors rather than shell command strings.

*Shell output.* Sourceable environment values are quoted for the selected shell. NUL and line-break characters are rejected rather than emitted into a pidfile or `--eval` output.

*--ssh-allow-forwarded.* Adopting a forwarded agent socket on a remote host means the *source* machine -- the laptop you forwarded from -- can sign authentication requests on this host's behalf. That is by design (it's the point of agent forwarding) but it widens the trust boundary. Don't enable on hosts you don't fully control.

*Coordination security.* The coordinated unlock protocol uses private FIFO endpoints for notification and operating-system advisory locks for ownership. Each attempt's held lock establishes that it is active; PID, hostname, heartbeat, and saved file contents do not establish ownership. The shared activation lock prevents SSH clearing during loading. A FIFO wakeup only signals a possible state change, so terminals check the agent before reporting that another attempt supplied their keys. `--no-lock` deliberately disables these guarantees.

*What Keychain does not protect against.* Root, the current user, or a process already able to act as that user can reach or replace user-owned resources. A malicious program selected through `PATH`, an intentionally unsafe command-line or preference-file setting, and a compromised agent are also outside the trust boundary. Any process that can reach an unlocked agent socket may ask it to sign; use agent timeouts, confirmation constraints, screen locking, and careful forwarding policy as appropriate.

== @topic agents: How keychain discovers, attaches, and spawns ssh-agent [category=reference, priority=50]

*Discovery order.* When `add` or `agent start` runs, keychain probes for an existing agent in this order:

1. The pidfile in `$KEYCHAIN_DIR/HOST-pid` (its writer left `SSH_AUTH_SOCK` and `SSH_AGENT_PID` here). Socket presence on disk and reachability via `ssh-add -l` are both verified.
2. Inherited `$SSH_AUTH_SOCK` / `$SSH_AGENT_PID` from the parent shell's environment, unless `--no-inherit` is set. Forwarded agents (`ssh -A`) are accepted only when `--ssh-allow-forwarded` is set. An inherited `gpg-agent` SSH socket is deliberately ignored.
3. None of the above -- a new `ssh-agent` is spawned. By default, its socket is pinned to `~/.keychain/HOST-agent.sock` instead of an ephemeral `/tmp/ssh-*` directory.

*Pidfile is the source of truth.* Per-host (so NFS-shared homes work) and per-user (mode 700). Older pidfiles are replaced atomically; no in-place edits. The default managed `ssh-agent` socket lives beside these files as `HOST-agent.sock`.

*Coordinated key loading.* A short-lived state lock protects metadata updates. Per-attempt locks identify live loading operations; the activation lock allows concurrent loading but excludes SSH clearing. Waiting terminals are notified through FIFO endpoints and verify the requested keys in the agent. See `keychain man topic:coordination`.

*Agent responsibilities.* Keychain owns `ssh-agent` discovery, startup, and coordination for SSH keys. For native GPG signing and decryption, Keychain invokes `gpg` and leaves `gpg-agent` startup, configuration, pinentry, and lifecycle entirely to GnuPG (this is new as of 3.0.1). It deliberately does not use GnuPG's SSH-agent emulation because adding an SSH key there imports the private key into GnuPG's persistent key store, whose behavior differs from the in-memory model users expect from `ssh-agent`.

== @topic extkeys: Extended key syntax [category=reference, priority=50]

SSH and GPG keys can be specified as positional arguments for the `add` action. `keychain add NAME` will look for `~/.ssh/NAME` if it exists and treat it as an SSH key; otherwise, it is looked up via `gpg --list-secret-keys` and if found the GnuPG signing key will be loaded and warmed. This *may* also warm an associated encryption key, but this is not guaranteed -- see below.

In addition to explicit passing of keys by name or path, positional key arguments may accept seven extended key prefixes to avoid ambiguity and provide more fine-grained control. The following prefixed formats can be mixed freely with regular positional key names:

    sshk:PATH       an SSH key file path
    sshk:NAME       an SSH key name under `~/.ssh/`
    gpgk:KEYID      a GnuPG key ID (legacy alias for `gpgs:`)
    gpgs:KEYID      warm and prove signing capability using GnuPG's signing path
    gpge:KEYID      warm and prove encryption/decryption capability
    gpga:KEYID      warm and prove signing + encryption/decryption capability
    pkcs11:PATH     a PKCS#11 provider library loaded via `ssh-add -s`
    host:HOSTNAME   every IdentityFile for HOSTNAME
                    (resolved via `ssh -G HOSTNAME`)

*Using the extended key syntax opens up several important features to help ensure that your specified GnuPG signing or encryption key is actually loaded and ready for use.* This is important because GnuPG's keys and subkeys can be tricky -- OpenPGP encryption capability may live on the primary key or on a separate encryption subkey, and GnuPG's passphrase cache behavior can vary by version, platform, and key layout. A signing operation may happen to unlock later decryption on some systems, but Keychain does not rely on that as a portable guarantee.

Extended key syntax also covers PKCS#11-backed SSH tokens. `pkcs11:PATH` asks OpenSSH to enumerate public keys from the provider with `ssh-keygen -D` and compare their fingerprints with the identities already held by the running agent. If the token's identities are not already present, Keychain loads the provider with `ssh-add -s PATH` so the usual PIN or touch prompt can occur in the foreground.

For GPG keys, `gpgk:KEYID` is equivalent to `gpgs:KEYID`. Both perform a signing-oriented warm-up: Keychain asks GnuPG to perform a signing operation with the requested key, which causes `gpg-agent` to cache the passphrase needed for signing. *This proves signing capability, but it does not prove that the same key can decrypt data.*

Using `gpge:KEYID` performs a similar explicit check to ensure functionality. When used, *Keychain will encrypt a tiny temporary payload with the requested key and then decrypt it to ensure that it is properly loaded and functional, and will fail if it does not succeed.* If your workflow decrypts data -- for example, `pass show ...` -- use `gpge:KEYID`.

If your workflow needs both signing and decryption, use `gpga:KEYID`. *Keychain will first warm the signing path, then require the same explicit decryption check performed by gpge:.* If GnuPG cannot encrypt to and decrypt with the requested key, `gpga:KEYID` fails rather than treating signing warm-up as enough.

`host:HOSTNAME` can be used to refer to a specific hostname defined in your local OpenSSH config. `host:` expansion is delegated to `ssh -G` so the full OpenSSH semantics (`Match`, `Include`, `%d`/`%u`/`%h` substitution, quoted args) apply properly.

== @topic pidfile: Pidfile location and contents [category=reference, priority=50]

Per user, per host, mode 700 directory:

    ~/.keychain/

(overridable by `--dir` / `[paths] dir`).

*Traditional pidfiles.* Files inside, where `HOST` is the resolved hostname:

    HOST-sh          Bourne-style `KEY=value; export KEY`
    HOST-csh         C-shell `setenv KEY value`
    HOST-fish        fish `set -x KEY value`
    HOST-envfile     bare `KEY=value` export for non-shell tools
    HOST-agent.sock  managed default `ssh-agent` socket path

All files are owner-only (mode 600) and re-written atomically.

One pidfile per host means that homedirs shared over NFS across many machines each get their own agent state -- no ssh-agent confusion when you log in from multiple hosts.

*Coordination state (3.0.0_beta2+).* For coordinated multi-terminal initialization:

    HOST.state.json      latest attempt identifier and recorded outcome
    HOST.state.lock      protects state file and waiter registration
    HOST.activation.lock shared for loading, exclusive for clearing
    HOST-waiters/        per-attempt locks and private FIFO endpoints

The coordination state file is a result record, not a list of live processes or registered waiters. Held per-attempt locks identify live operations, and each terminal's notification FIFO is its registration. Coordination files are created with mode 0o600 inside the private keychain directory. Normal cleanup removes the attempt's temporary resources; files left by a terminated process are cleaned up after its lock is no longer held.

== @topic env: Environment emission and shell integration [category=reference, priority=50]

Keychain sets `SSH_AUTH_SOCK` and `SSH_AGENT_PID` so `ssh`, `scp`, and related tools can find the running agent. The `--eval` flag emits environment assignments for your shell to execute. Interactive terminals load your keys; non-interactive login shells establish the agent environment without prompting. This distinction lets graphical-session startup finish even when it runs a login shell whose terminal you cannot access.

Choose the recipe for your shell and replace the filenames in `KEYCHAIN_KEYS` with your keys. Keep one key per line and quote filenames containing spaces. The key list is a shell variable and does not need to be exported. Keep unrelated existing configuration, and replace any older Keychain startup invocation rather than adding a second one.

*Bash startup.* Add this to `~/.bash_profile`, keeping any existing configuration:

```
KEYCHAIN_KEYS=(
    ~/.ssh/id_ed25519
    ~/.ssh/id_rsa
)

if [[ $- == *i* ]]; then
    eval "$(keychain add --eval "${KEYCHAIN_KEYS[@]}")"
else
    eval "$(keychain add --eval --noask)"
fi
```

Bash reads `.bash_profile` for login shells, including non-interactive ones. The `i` in `$-` identifies an interactive shell; `--noask` skips key loading in the other branch.

*Zsh startup.* Add this to `~/.zshrc`, which is read only by interactive shells:

```
KEYCHAIN_KEYS=(
    ~/.ssh/id_ed25519
    ~/.ssh/id_rsa
)
eval "$(keychain add --eval "${KEYCHAIN_KEYS[@]}")"
```

If your graphical session also needs the agent environment, add `eval "$(keychain add --eval --noask)"` to `~/.zprofile`. That login-shell file can run non-interactively; key loading belongs in `.zshrc`.

*Fish startup.* Add this to `~/.config/fish/config.fish`, keeping any existing configuration:

```
set KEYCHAIN_KEYS \
    ~/.ssh/id_ed25519 \
    ~/.ssh/id_rsa

if status is-interactive
    eval "$(keychain add --eval $KEYCHAIN_KEYS)"
else if status is-login
    eval "$(keychain add --eval --noask)"
end
```

Fish reads `config.fish` for every shell. This recipe loads keys only in interactive shells, uses `--noask` for non-interactive login shells, and skips Keychain in other non-interactive shells. This avoids the inaccessible startup prompt reported with SDDM graphical login.

*Bash alternative: load keys from .bashrc.* Use this arrangement if you want both interactive login shells and interactive non-login terminals to run Keychain. In `~/.bash_profile`, source `.bashrc` for interactive logins and establish the agent environment for non-interactive logins:

```
if [[ $- == *i* ]]; then
    if [[ -f ~/.bashrc ]]; then
        source ~/.bashrc
    fi
else
    eval "$(keychain add --eval --noask)"
fi
```

In `~/.bashrc`, keep the key list and interactive key loading together:

```
KEYCHAIN_KEYS=(
    ~/.ssh/id_ed25519
    ~/.ssh/id_rsa
)

if [[ $- == *i* ]]; then
    eval "$(keychain add --eval "${KEYCHAIN_KEYS[@]}")"
fi
```

Place the `.bashrc` block after any existing early return for non-interactive shells. If `.bash_profile` already sources `.bashrc`, use the conditional source above in place of that existing source command. Keychain then runs once per interactive shell, while non-interactive logins still receive the agent environment without loading keys.

*Alternate Option 1: Source the per-host pidfile directly.* If you prefer not to invoke Keychain in every shell, source the pidfile that Keychain writes after the first run. For Bourne-style shells, run `. ~/.keychain/$(hostname)-sh`; for fish, run `source ~/.keychain/$(hostname)-fish`. This is faster because it avoids a Python invocation, but it requires that Keychain has already run at least once to create the pidfile.

*Alternate Option 2: Push to systemd user environment.* If your session uses systemd, you can push the agent environment to all child units via `keychain add --systemd id_rsa`. This runs `systemctl --user set-environment` to set `SSH_AUTH_SOCK` and `SSH_AGENT_PID` for all systemd services and timers. To update the systemd environment without loading keys, use `keychain env --target systemd` and redirect to an environment file.

*Alternate Option 3: Emit bare KEY=value for tools.* For non-shell tools that consume `EnvironmentFile=` or similar formats, use `keychain env > /run/user/$UID/keychain.env` to generate a bare definition file to be sourced by another tool. The default `env` target outputs bare `KEY=value` lines suitable for systemd's `EnvironmentFile=` directive or other configuration parsers.

== @topic compat: keychain 2.x compatibility [category=appendix, priority=50]

Every keychain 2.x flat-flag invocation is translated to the new-style action verbs transparently.

Old long-form spellings of options that have been renamed (`--noask`, `--noinherit`, `--nogui`, `--nolock`, `--no-color`) all remain accepted. *No deprecation is planned for the legacy flag spellings.*

Old options that are now hidden but still accepted for compatibility: `--extended`, `--gpg2`, `--absolute`, `--clear`, `--theme`, `--ssh-allow-forwarded`, `--no-inherit`, `--ssh-agent-socket`, `--confirm`. These are now lifestyle preferences set in `~/.keychainrc`; the CLI flags work but no longer appear in --help. `--clear` follows Keychain 3's SSH-only default wipe policy; clearing GnuPG remains an explicit `wipe --gpg` operation.

Deprecated no-ops retained for command-line compatibility: `--ssh-allow-gpg` and `--ssh-spawn-gpg`. Each emits a warning and is ignored; Keychain uses `ssh-agent` exclusively for SSH keys.

Removed with migration guidance: `--confhost`; use `host:HOSTNAME` instead.

Other deprecated no-ops retained with a warning: `--agents`, `--inherit`, and `--attempts`.

*Coordination model change (3.0.0_beta2+).* The coordinated unlock protocol introduced in 3.0.0_beta2 fundamentally changes how multiple terminals initialize keys simultaneously. In Keychain 2.x and 3.0.0_beta1, terminals raced for a single lock with losers timing out after 5 seconds. In 3.0.0_beta2+, terminals cooperate via FIFO notification and all succeed together. Legacy invocations still work, but the behavior is now cooperative rather than competitive. Users will notice the absence of `Waiting N seconds for lock...` messages during interactive key loading.

Compatibility is therefore intentionally *high* but not perfect. In particular, bare tokens still default to `add` for 2.x compatibility, but if none of the requested keys resolve, keychain refuses to start an agent and exits with an error instead of preserving the old side effect.

== @topic windows: Windows-specific notes [category=platform, priority=50]

Keychain runs on Windows Subsystem for Linux (WSL) and Git Bash on Windows via `keychain.pyz` plus the Python launcher (`py.exe`). What you get:

* The full action surface (`add`, `agent start`, `list`, `inspect`, `env`, ...). The embedded documentation system (`--help`, `man`, `--explain`) works identically to the Linux build.
* Coordinated multi-terminal initialization (3.0.0_beta2+) works on WSL and Git Bash. When multiple terminals start simultaneously (common when Visual Studio Code reconnects to WSL), they coordinate automatically to prevent `could not acquire lock` errors.
* WSL requires the standard Linux OpenSSH tools (`ssh-agent`, `ssh-add`) installed in your WSL distribution.
* Git Bash uses the MinGW environment and may require Git for Windows' SSH tools.

== @topic config: ~/.keychainrc preference file [category=reference, priority=50]

Keychain 3 introduces an optional configuration file, `~/.keychainrc`, for setting persistent defaults. The config file allows users to customize Keychain's behavior across all invocations. Keychain reads `~/.keychainrc` at startup. The file uses INI-style syntax with `[section]` headers and simple key/value pairs:

```
[agent]
timeout = 1h

[lock]
lock = true

[agent.env]
ssh_args = -a ~/.ssh/agent.sock
```

*Evaluation order.* Keychain evaluates settings in this order, with successive steps overriding previous steps:

1. Built-in defaults provide the base settings, including any package-selected activation default.
2. If `--allow-env` is specified, `$KEYCHAIN_CONFIG` can point to an alternate `.keychainrc` path.
3. The selected `.keychainrc` file is parsed.
4. If `--allow-env` is specified, supported `KEYCHAIN_*` variables from the environment are applied.
5. Explicit command-line flags are applied last.

This means command-line flags always override environment and config file settings, giving you flexibility to customize individual invocations while maintaining sensible defaults.

*Environment variables.* Keychain does not support arbitrary `KEYCHAIN_*` environment variables. The supported environment surface is intentionally small:

* `KEYCHAIN_CONFIG` -- alternate path to the config file (instead of `~/.keychainrc`)
* `KEYCHAIN_SSH_AGENT_ARGS` -- extra arguments for spawned `ssh-agent`

By default, Keychain ignores these variables for security reasons. To enable them, pass `--allow-env` (or `-E`) on the command line. This prevents untrusted shell environments from silently altering Keychain's behavior. Normal interactive users should prefer `~/.keychainrc` for persistent settings.

*Config key correspondence.* Most config keys mirror command-line options. For example, `--timeout 1h` on the command line is equivalent to `timeout = 1h` in the `[agent]` section. When a config key corresponds to a CLI option, the documentation references that option for behavioral details.

Some boolean config keys use positive phrasing even when the historical CLI flag is negative for compatibility. For instance, `[lock] lock = false` produces the same behavior as `--no-lock`.

@func:config_index

*Advanced config settings.* A few settings exist only in the config file, with no command-line equivalent. These include:

* `pid_formats` in `[paths]` -- comma-separated pidfile formats to write after agent setup.
* `ssh_args` in `[agent.env]` -- extra arguments passed when spawning `ssh-agent`. This is injected into Keychain's internal environment as `$KEYCHAIN_SSH_AGENT_ARGS`, which the agent startup code reads. If this environment variable is set in the outer environment and `--allow-env` is active, it takes precedence over the config file value.

* Advanced tuning parameters for coordination and locking behavior

*Value formats.* Boolean values accept: `true`, `false`, `yes`, `no`, `on`, `off`, `1`, `0`. Integers are decimal. Strings are passed verbatim (quote values containing spaces, though unquoted usually works).

*Editing and discovery.* Edit `~/.keychainrc` directly with your preferred text editor. Use `keychain man --list` to see all available config keys, or `keychain man config:SECTION.KEY` for detailed documentation on a specific key. The config key list is generated from Keychain's option metadata, so it stays synchronized with the implementation.
