KEYCHAIN(1)
π KEYCHAIN(1) π
okay bestie, buckle up β this is KEYCHAIN(1), straight out of the User Commands section of the manual, and it's about to change your whole terminal life. no cap.
πΈ NAME πΈ
keychain β Manager for ssh-agent and private keys, with native GnuPG support
β¨ SYNOPSIS β¨
keychain [global-options] [action] [action-options] [keys...]
π¦ DESCRIPTION π¦
Keychain orchestrates ssh-agent and gives you one coordinated, long-running SSH agent per user and host. It's a frontend to ssh-agent and ssh-add that lets you keep one long-running ssh-agent process per user per host β instead of the default of one per login session β so you only need to enter each key's passphrase once per boot. literally once. iconic behavior π
π 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. fr fr, your cron jobs eat with this one π₯
π§ GnuPG, but make it convenient π§
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. π«Ά
heads up bestie: GnuPG features require GnuPG 2.1 or later. no exceptions, the vibes demand it.
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. like, keychain is not stepping on gpg-agent's turf β that's its main character era, we respect it π
β the multi-terminal glow-up β
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 β Enter in any one terminal to initialize keys, and all others are notified when complete. the way that just works now π
See keychain man topic:coordination. π
π INTRODUCTION π
okay so before we get into the actual key-loading chaos, here's the whole vibe check for Keychain 3, bestie β¨
π Keychain 3, Requirements and Backward Compatibility (topic:requirements) π
Keychain supports most UNIX-like operating systems and intentionally limits its scope to modern, widely deployed implementations of OpenSSH and GnuPG. no dusty legacy nonsense, just the stuff people actually run π
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 <code>.pyz</code> file that runs on any system with Python 3.9 or newer, without requiring external Python dependencies. It's giving modern runtime while maintaining the self-contained, script-based nature of Keychain, periodt π«Ά
Action-driven interface. Keychain 3 introduces a new action-driven interface (<code>keychain add id_rsa</code>, <code>keychain agent stop --mine</code>, <code>keychain wipe --ssh</code>, etc.) and is now the recommended way to drive Keychain. A full embedded documentation engine provides help via <code>--help <action></code> or <code>--explain</code> for context-sensitive documentation. The complete man page is available via <code>keychain man</code>, with <code>keychain man --list</code> showing all available sections. documentation slay π
Backward compatibility. Maintaining <code>keychain 2.x</code> CLI compatibility was a primary goal. The legacy flat-flag command line (for example <code>keychain --eval --quiet id_rsa</code>) continues to work exactly as before via an explicit compatibility shim. no cap, your old scripts are safe π«‘
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. shells stay fed, bestie π
πΈ General Usage (topic:usage) πΈ
Typically, you configure Keychain to run from your shell startup files. Use the shell-specific recipes in <code>keychain man topic:env</code> 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 <code>ssh-agent</code> if one isn't already running, then checks to make sure your private keys (in this example, <code>id_rsa</code>) 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. lowkey iconic behavior π
In addition to printing user-friendly output to your terminal, <code>keychain</code> outputs important <code>ssh-agent</code> environment variables. Command substitution captures this output, and the shell built-in <code>eval</code> evaluates it, causing these variables to persist in your current session. it's giving "the shell remembers so you don't have to" energy β¨
These <code>ssh-agent</code> environment variables are also written to <code>~/.keychain/${HOSTNAME}-sh</code>, so that subsequent logins and non-interactive shells such as cron jobs can source this file to access the running <code>ssh-agent</code> and make passwordless ssh connections using the cached private keys β even when you are logged out. These files are collectively called pidfiles. yes bestie, your cron jobs can vibe with your agent too π
Multi-terminal coordination (3.0.0_beta2+). When multiple shells start simultaneously, all terminals coordinate automatically. Each displays <code>Press Enter to run ssh-add in this terminal</code>. Pressing Enter in any one terminal runs <code>ssh-add</code>; other terminals wait and are notified when complete. This eliminates the <code>could not acquire lock</code> errors from earlier versions. See <code>keychain man topic:coordination</code>. no more lockfile scavenger hunt, we love to see it π
The key files specified on the command-line will be searched for in the <code>~/.ssh/</code> directory, and <code>keychain</code> will expect to find the private key file with the same name, as well as a <code>.pub</code> 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. bestie, it checks both sides of the key situation π¦
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 <code>pkcs11:/path/to/provider.so</code>; Keychain will compare the token's exported public keys against the current agent and, when needed, load the provider via <code>ssh-add -s</code>. fancy token girlies, this one's for you π
For GPG keys, Keychain exercises the requested capability so <code>gpg-agent</code> has the required passphrase cached and ready for use. Use <code>gpgs:KEYID</code> for signing-oriented workflows, <code>gpge:KEYID</code> for decrypt-oriented workflows such as <code>pass</code>, and <code>gpga:KEYID</code> when both capabilities should be warmed. The <code>gpge:</code> prefix performs an explicit encrypt-then-decrypt verification cycle β a tiny temporary payload is encrypted to the key and immediately decrypted through <code>gpg-agent</code>. If this verification cannot be completed, <code>add</code> 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 <code>gpgk:KEYID</code> alias is equivalent to <code>gpgs:KEYID</code> (signing warm-up only). gpg bestie said "verify, don't guess" π₯
π¦ Coordinated key initialization (3.0.0_beta2+) (topic:coordination) π¦
The problem. When multiple terminal shells start simultaneously, each shell may run <code>keychain add --eval</code> to initialize SSH keys. In Keychain <code>2.x</code> and <code>3.0.0_beta1</code> 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 <code>could not acquire lock</code>. Users had to manually delete the lockfile or retry. it was giving "everyone fighting over one bathroom stall" energy and nobody was having a good time π
This was especially problematic in Visual Studio Code: only one terminal is visible at a time, so the terminal running <code>ssh-add</code> 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. not the scavenger hunt, bestie π
The solution (3.0.0_beta2+). Keychain now uses a coordinated unlock protocol designed specifically for multi-terminal scenarios:
- 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. short lock, no drama π«Ά
- 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 <code>wipe</code> to clear the agent during loading. boundaries, bestie π
- 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. they're literally passing notes in class, but secure π
- 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. okay so we're passing the aux cord, but for passphrases π§
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 <code>ssh-add</code>. 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.
that "wake automatically" moment? highkey satisfying π
Automatic activation. <code>--immediate</code> skips Keychain's initial Enter prompt. Several terminals can run <code>ssh-add</code> 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. main character energy stays with the user, as it should π
Implementation details. The latest completion record is stored in <code>~/.keychain/<host>.state.json</code>; 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 <code>~/.keychain/<host>-waiters/</code>. The shared loading/exclusive clearing lock is <code>~/.keychain/<host>.activation.lock</code>. Runtime files are private to the current user and are checked before use. security bestie checked the vibes and the permissions β
Scope. The coordinated unlock flow applies to SSH key loading only. GPG keys use explicit warm-up paths (<code>gpgs:</code>, <code>gpge:</code>, <code>gpga:</code>) and do not participate in multi-terminal coordination. SSH only, no crossover episode π¦
Legacy behavior. Users upgrading from Keychain <code>2.x</code> or <code>3.0.0_beta1</code> will notice the absence of <code>Waiting N seconds for lock...</code> messages during interactive key loading. This is intentional: internal coordination is quiet, and the state lock is held only briefly for metadata updates. so yeah, the silence is a feature, not a bug π
β¨ GLOBAL OPTIONS β¨
okay bestie, these are the global flags β the main-character options that apply no matter which action you're doing. all the deets, no cap π
--help,-hβ prints the top-level help cheat sheet, or action-specific help when it's followed by an action path.-his literally the alias π«Ά--version,-Vβ prints the version banner and exits.-Vis the alias, periodt.--explainβ this global one tells keychain to not actually act on the command line. instead it evaluates the command-line arguments (minus the--explainoption) and prints documentation boxes for the action, every recognised option and positional argument to standard output. essentially, it tells Keychain to explain the command invocation it would have performed to you by printing all relevant documentation snippets. it's giving "read me the recipe before we cook" energy β¨--allow-env,-Eβ by default, Keychain silently ignores allKEYCHAIN_*environment variables to prevent accidental or malicious injection in multi-user environments. pass-Eor--allow-envto allow$KEYCHAIN_CONFIGto override the config file path and$KEYCHAIN_SSH_AGENT_ARGSto inject extra flags into a spawnedssh-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. so likeβ¦ don't just leave this on for funsies, bestie π₯
--quiet,-qβ silences routine Keychain messages and successfulssh-addreports. required interactive prompts, explicit results from actions such aslist, warnings, and errors are still printed. she's quiet, not gone π--debug,-Dβ emits extra details about the agent-discovery decisions keychain makes: which pidfile candidates were considered, which sockets were probed, why one was selected, whatssh -Greturned for each host: extkey, etc. detective-mode keychain π--debug-logβ use--debug-log FILEto 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--evalare 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. do not forget the cleanup step, okay? π«Ά
--nocolor,--no-colorβ disables 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_COLORenvironment variable (https://no-color.org) is also honored; the flag wins when both are set.--no-coloris an accepted alias. bye-bye rainbow, hello clean bytes πβ‘οΈπ--themeβ selects the color / glyph theme.legacyis the keychain 2.x output style;modernis the 3.0 default. a once-forever lifestyle preference; usually set in~/.keychainrc. choose your aesthetic and commit, bestie π--no-gui,--noguiβ 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. no pop-up jumpscares, just terminal honesty π--absoluteβ treats--dir(or[paths] dir) as the literal pidfile directory; don't append/.keychain. literal queen behavior π--dirβ overrides the per-user state directory. by default keychain uses~/.keychain/for pidfiles, env-files, and lock files.--absolutetreats the value verbatim instead of appending/.keychain.--host,--hostnameβ--host HOSTNAME(or the more explicit--hostname HOSTNAME) overrides the hostname used in pidfile names. pidfiles are namedHOST-sh/HOST-csh/HOST-fishetc. so multiple hosts sharing a homedir over NFS each get their own state. by default keychain probeshostname(1),$HOSTNAME, thenuname -n. identity crisis? avoided π¦
π ACTIONS π
Okay bestie, this is the main character section β every action Keychain can do, one by one. Buckle up. π
π keychain add
Syntax:
keychain add [OPTIONS] [KEYS...]
add is the workhorse of Keychain 3 β no cap, it carries the whole team. Back in Keychain 2.x, this was the implied action when you didn't specify any other action. It tells Keychain to first make sure an ssh-agent is currently running (which is literally equivalent to an automatic keychain agent start). If there isn't one? Boom, one gets 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 β that's its lane, not Keychain's.
- 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. It's giving teamwork, honestly:
- All terminals that detect missing SSH keys display:
Press Enter to run ssh-add in this terminal. - Pressing Enter in any terminal designates it to run
ssh-add. - Other terminals wait automatically and are notified when initialization completes.
- 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. Iconic behavior, honestly.
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. (We love a safety net. π
) 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. GPG said "not me participating in your group project," and honestly valid. π
See also: topic:extkeys, topic:coordination.
π keychain agent
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.
keychain agent start
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.
keychain agent stop
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.
π keychain env
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, bestie:
envβ bareKEY=valuelines (default)shβ Bourne shellKEY=value; export KEYcshβ C-shellsetenv KEY valuefishβset -x KEY valuejsonβ JSON object on one lineevalβ same asadd --eval: shell-detected dialectsystemdβ bareKEY=value(suitable forEnvironmentFile=)
--target is an alias for --shell; --json is shorthand for --shell json.
π keychain forget
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. Sorry bestie, them's the rules. π
Without arguments, forget is a no-op (use wipe to remove all keys).
π keychain 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.
π keychain inspect
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. Perfect for when you need to spill the tea to a maintainer. π
π keychain list
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. Aesthetic and functional, we love to see it. β¨
--json emits a JSON array suitable for scripting.
π keychain man
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 manualkeychain man <action>β one action (incl. all flags)keychain man <topic>β named topical sectionkeychain man topic:<name>β same, with explicit prefixkeychain man --listβ index every doc atomkeychain man --no-pagerβ skip$PAGEReven on TTYkeychain 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. No glow-up for them, sadly. π
π keychain version
Syntax:
keychain version [--json]
Print the version string, copyright, and license summary.
π keychain wipe
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 onlywipe --sshβ SSH identities onlywipe --gpgβ gpg-agent's in-memory secret cache onlywipe --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 OPTIONS β‘
--eval
Syntax:
keychain add --eval [KEYS...]
This is a foundational keychain feature β like, genuinely foundational, bestie. 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. iykyk π
--systemd
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)"
Systemd services getting the agent env without lifting a finger? That's main character energy for your background processes. π
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 pop a prompt for keys. π«Ά
Real talk: --systemd is a side-effecting operation that runs systemctl directly β it does not print any shell code. If all you want is to update the systemd environment without loading keys, use keychain env --target systemd instead; that one prints the agent environment in systemd EnvironmentFile= format (bare KEY=value lines). β¨
--quick,-Qβ if a reachablessh-agentalready holds at least one key, just leave. Exit immediately, no prompting, no loading anything new. It's built for shell-startup snippets where the agent gets reused across a million shells, so the shell only pays the cost of a singlessh-add -lon subsequent invocations. π Heads up: this is intentionally an SSH-only compatibility option. GPG key arguments get ignored and Keychain does not invoke GnuPG at all when--quickis present. For GPG workflows, use normalkeychain add. π--immediateβ when requested SSH keys are missing, startssh-addwithout displayingPress Enter to run ssh-addin this terminal. Immediate invocations can prompt independently, so a hidden terminal's passphrase request doesn't block a visible terminal from loading keys. And after another terminal finishes loading, each invocation checks its own requested keys and cancels its unfinishedssh-addif those keys are now available. Coordination, but make it fast. π₯ This option does not suppress anssh-addpassphrase prompt, hide errors, disable coordination, or imply--quiet. For quiet automatic activation, add--quiet --immediateto the interactive branch of your startup recipe (seekeychain 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 keep going 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, but it does not reconcile differing settings: if the same identity gets 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. π
--no-passphrase,--noaskβ 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. Which means the command can succeed even when a requested key isn't available β this option does not verify key availability. π For a modern agent-only invocation, preferkeychain agent start. The--noaskspelling and its--no-passphrasealias are here to preserve the traditional Keychain 2.x behavior. π--confirmβ passes-ctossh-add: every subsequent use of the loaded key (say, byssh,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. No vibes-based approval here, bestie. π
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 doesn't 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. π
--confirmcannot be combined with--no-gui. Keychain reports an error instead of silently weakening the request or loading a key whose confirmation constraint can't be satisfied. Safety first, always. π
--timeoutβ passes a timeout straight through tossh-add: the key is forgotten by the agent after the requested number of minutes. Common settings include480(8h, a workday) or60(higher security). Also a common config-file preference. β°--ignore-missingβ for each KEY positional that doesn't resolve to an existing file (or known GPG ID), skip it silently instead of erroring. Perfect for dotfiles shared across machines where not every key exists everywhere. And if all requested keys are missing,addstill exits successfully without spawning an agent. No drama. π¦--clearβ remove every identity from the selectedssh-agent, then process the requested keys normally. Equivalent to calling:
keychain wipe && keychain add KEYS
Important: --clear does not flush GnuPG's passphrase cache, even when GPG keys are also requested. Use keychain wipe --gpg when that separate operation is what you actually mean. π§Ή
--confallhostsβ iterate everyHostblock in~/.ssh/config(excluding pure-wildcard patterns and negations) and load itsIdentityFile. Equivalent to--extended host:NAMEfor every host. Per-host expansion is delegated tossh -G NAME, so include files,Matchblocks, and%d/%u/%htokens work exactly as OpenSSH does. iykyk. π--ssh-allow-forwardedβ adopt the inherited$SSH_AUTH_SOCKif it points to a forwarded agent (typical ofssh -Afrom a workstation into a server). Heads up, this has security implications: the forwarded socket is controllable by the source machine. Seekeychain man topic:security. β οΈ--no-inherit,--noinheritβ don't trust$SSH_AUTH_SOCK/$SSH_AGENT_PIDfrom the parent environment; always start fresh from the pidfile or by spawning. The inverse of--ssh-allow-forwarded. π«‘--ssh-agent-socketβ use PATH as thessh-agentsocket explicitly. When this option isn't supplied, Keychain now chooses a host-specific managed socket path under~/.keychain/instead of lettingssh-agentpick a transient location under/tmp. Override it only when integrating with another tool that expects a specific socket path. π--lockwaitβ maximum time (in seconds) to wait for keychain's state lock when another keychain process is mutating shared coordination state. Default5seconds. β³ Historical note, gather round: in Keychain 2.x and 3.0.0_beta1,--lockwaitcontrolled the timeout for the single global lock that protected the entire key-loading operation. That was the source of thecould not acquire lockerrors 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. So--lockwaitis now an internal tuning parameter that most users will never need to adjust. Set it to0to fail immediately if the state lock is held (rare in normal operation). π--mineβ 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. π«Ά--othersβ inverse of--mine: terminate only agents whose owner UID does not match the calling user. Rare; typically a maintenance tool. π§--allβ terminate every candidate agent recorded in the pidfile, regardless of owner. This is the default target forkeychain agent stop. π--shell,--targetβ specify the output format forkeychain env. Supported targets:envβ bareKEY=valuelines (default, suitable forEnvironmentFile=)shβ Bourne shellKEY=value; export KEYcshβ C-shellsetenv KEY valuefishβ fish shellset -x KEY valuejsonβ JSON object on one lineevalβ shell-detected dialect (same asadd --eval)systemdβ bareKEY=value(alias forenv, suitable for systemd)
Use --target systemd or --shell systemd when you want to redirect output to a file for 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. β¨
--jsonβ equivalent to--shell json. π--jsonβ emit the inspection payload as JSON. (yes, again β she contains multitudes) π--jsonβ emit a JSON array of key records on stdout. π₯--listβ printslug-- one-liner for every action, global flag, config key, and topic. Useful as a discovery surface. πΈ--no-pagerβ skip the automatic$PAGERinvocation even when stdout is a terminal. π«--widthβ explicit wrap width. Defaults to$COLUMNSthen80. π--roffβ write the manual as UTF-8 roffman(7)source, for installation askeychain.1. Never paged, and--widthdoes not apply.$SOURCE_DATE_EPOCHpins the header date.
keychain man --roff > keychain.1
--jsonβ emit{"version": "X.Y.Z"}. π·οΈ--sshβ remove all identities held byssh-agentusingssh-add -D. This does not delete the corresponding private key files. ποΈ--gpgβ sendRELOADAGENTto an already-runninggpg-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. π
π REFERENCE TOPICS
πͺ Windows-specific notes (topic:windows)
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 several terminals start at the exact same moment (super common when Visual Studio Code reconnects to WSL), they coordinate with each other automatically so you don't get those
could not acquire lockerrors. iconic behavior honestly. - WSL needs the standard Linux OpenSSH tools (
ssh-agent,ssh-add) installed inside your WSL distribution, no cap. - Git Bash lives in the MinGW environment and may need Git for Windows' SSH tools.
π How keychain discovers, attaches, and spawns ssh-agent (topic:agents)
Discovery order. When add or agent start runs, keychain goes probing for an existing agent, and it checks in this order:
- The pidfile in
$KEYCHAIN_DIR/HOST-pidβ its writer leftSSH_AUTH_SOCKandSSH_AGENT_PIDsitting in there. Both socket presence on disk and reachability viassh-add -lget verified, bestie. No vibes-based trust here. - Inherited
$SSH_AUTH_SOCK/$SSH_AGENT_PIDfrom the parent shell's environment β unless--no-inheritis set. Forwarded agents (ssh -A) are only accepted when--ssh-allow-forwardedis set. And an inherited gpg-agent SSH socket? Deliberately ignored. not invited to the party π . - None of the above β so a brand new
ssh-agentgets spawned β¨. By default its socket is pinned to~/.keychain/HOST-agent.sockinstead of some chaotic ephemeral/tmp/ssh-*directory.
Pidfile is the source of truth. Per-host (so NFS-shared homes actually work) and per-user (mode 700). Older pidfiles get replaced atomically β there are no in-place edits, ever. The default managed ssh-agent socket lives right 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 get notified through FIFO endpoints and then verify the requested keys in the agent. See keychain man topic:coordination for the whole saga.
Agent responsibilities. Keychain owns ssh-agent discovery, startup, and coordination for SSH keys, periodt. 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 β and that behavior differs from the in-memory model you expect from ssh-agent. gross, we don't want that π.
π Environment emission and shell integration (topic:env)
Keychain sets SSH_AUTH_SOCK and SSH_AGENT_PID so ssh, scp, and all their little friends can find the running agent. The --eval flag emits environment assignments for your shell to actually execute. Interactive terminals load your keys; non-interactive login shells establish the agent environment without prompting. That distinction is the whole point β it lets graphical-session startup finish even when it's running a login shell whose terminal you can't even reach π.
Choose the recipe for your shell and replace the filenames in KEYCHAIN_KEYS with your keys. Keep one key per line, and quote filenames that contain spaces. The key list is a shell variable and does not need to be exported. Keep your unrelated existing config, 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 that was 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'd rather not invoke Keychain in every shell, just 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, so like, no shortcuts before the first run bestie π.
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 π₯.
π Extended key syntax (topic:extkeys)
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's looked up via gpg --list-secret-keys, and if found, the GnuPG signing key gets loaded and warmed. This may also warm an associated encryption key, but that's not guaranteed β see below, bestie.
In addition to explicitly passing keys by name or path, positional key arguments may accept seven extended key prefixes to avoid ambiguity and give you 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 make sure your specified GnuPG signing or encryption key is actually loaded and ready to go. This matters because GnuPG's keys and subkeys are tricky little things β 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 aren't already present, Keychain loads the provider with ssh-add -s PATH so the usual PIN or touch prompt can happen 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. important distinction, don't skip it π
.
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 it's properly loaded and functional β and it will fail if it doesn't 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 the signing warm-up as enough. no half-vibes here π₯.
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 π.
π Pidfile location and contents (topic:pidfile)
Per user, per host, mode 700 directory:
~/.keychain/
(overridable by --dir / [paths] dir).
HOST-shβ Bourne-styleKEY=value; export KEYHOST-cshβ C-shellsetenv KEY valueHOST-fishβ fishset -x KEY valueHOST-envfileβ bareKEY=valueexport for non-shell toolsHOST-agent.sockβ managed defaultssh-agentsocket path
Every single one of these files is owner-only (mode 600) and gets re-written atomically, so no half-written mess, no cap. π«Ά
One pidfile per host is the whole point, bestie: homedirs shared over NFS across many machines each get their own agent state β which means no ssh-agent confusion when you log in from multiple hosts. It's giving organized. π
Coordination state (3.0.0_beta2+). For coordinated multi-terminal initialization:
HOST.state.jsonβ latest attempt identifier and recorded outcomeHOST.state.lockβ protects state file and waiter registrationHOST.activation.lockβ shared for loading, exclusive for clearingHOST-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. The way that works is honestly kind of iconic. β¨
πΈ CONFIG KEYS (~/.keychainrc) πΈ
π ~/.keychainrc preference file (topic:config) π
Keychain 3 introduces an optional configuration file, ~/.keychainrc, for setting persistent defaults. The config file lets you 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, okay so pay attention:
- Built-in defaults provide the base settings, including any package-selected activation default.
- If
--allow-envis specified,$KEYCHAIN_CONFIGcan point to an alternate.keychainrcpath. - The selected
.keychainrcfile is parsed. - If
--allow-envis specified, supportedKEYCHAIN_*variables from the environment are applied. - 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. Last one wins, periodt. π
π 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 spawnedssh-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 β which, honestly, thank you. 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. No cap, that's just how it is. π
agent.activationβ choose prompted or immediate SSH key activationagent.clearβ wipe SSH identities before addingagent.confirmβssh-add -c: prompt per useagent.inheritβ ignore inheritedSSH_AUTH_SOCK/ PIDagent.ssh_agent_socketβ use PATH as thessh-agentendpointagent.ssh_allow_forwardedβ adopt a forwarded SSH agentagent.systemdβ push agent env tosystemctl --useragent.timeoutβ auto-expire keys after MIN minutesagent.env.ssh_argsβ extra args passed to spawnedssh-agentkeys.confallhostsβ load everyIdentityFilein~/.ssh/configkeys.ignore_missingβ skip keys that don't existoutput.animateβ animate the initial activation promptoutput.colorβ disable ANSI color outputoutput.guiβ don't use GUI passphrase promptsoutput.quietβ suppress non-error output on stderroutput.themeβ color theme:legacyormodernpaths.absoluteβ treat--diras the literal pidfile dirpaths.dirβ pidfile directory (default:~/.keychain)paths.pid_formatsβ pidfile output formats
π Detailed config entries π
β agent.activation β
Config key: [agent] activation
--immediate selects activation = immediate for this invocation.
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. iykyk π«Ά
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.
π¦ agent.env.ssh_args π¦
Config key: [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. π
π output.animate π
Config key: [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. Obsessed with her, honestly. β¨
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.
π paths.pid_formats π
Config key: [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.
π Advanced config settings π
A few settings exist only in the config file, with no command-line equivalent. These include:
pid_formatsin[paths]β comma-separated pidfile formats to write after agent setup.ssh_argsin[agent.env]β extra arguments passed when spawningssh-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-envis 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). Fr fr, quote them if you're being fancy. πΈ
π₯ 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. Main character metadata behavior, no notes. π
π APPENDIX π
π§ keychain 2.x compatibility (topic:compat) π§
Okay bestie, this is the part where old Keychain energy gets translated into new Keychain energy β every single keychain 2.x flat-flag invocation is translated to the new-style action verbs transparently. No drama, just vibes. β¨
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. It's giving βwe're all in this togetherβ instead of βfight to the death for the lock.β πβ¨
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. That's the fine print, bestie β read it and weep, or read it and slay. π
π‘οΈ Security model (topic:security) π‘οΈ
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. Seriously. This is not the moment for main character energy. π
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. Iconic for debugging, lowkey terrifying as a permanent lifestyle. π
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. No cap β the socket is the VIP section, and you gotta check the guest list. π
And that's the whole manual, bestie. This page is from keychain 3.0.8, dated 2026-10-23, and it's the KEYCHAIN(1) page. Now go forth, keep your agent cozy, and don't let randos touch your sockets. πβ¨π
π¦ where this page comes from
- arch rolling: keychain 3.0.8-2 Β· GPL-2.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms