Keychain 3.0.2 is a focused maintenance release that improves command-line compatibility and validation, quiet startup behavior, and the visibility of Keychain's core value proposition.
- Corrected legacy command-line translation so options and their values remain in their original order. This prevents value-taking options such as
--timeout 180from being regrouped and misinterpreted when used with traditional Keychain 2.x-style invocations (PR #236). - Implemented missing validation for fixed lists of values. For example,
keychain env --shellnow rejects unsupported output formats and lists the valid choices instead of silently falling back to another format (fixes #234). - Made
--quietpass OpenSSH's native-qoption tossh-add, suppressing successfulIdentity addedreports while preserving passphrase prompts and errors (fixes #237). - Copied the visual "Keychain Difference" into the built-in manual, and optimized ordering of sections in the README (Discussion #218).
Keychain 3.0.1 continues to improve macOS --confirm UI dialog support. When --confirm is used on macOS, it now implies --no-inherit, to ensure that Keychain is able to initialize its own ssh-agent that is properly configured to use the macOS native Keychain confirm dialog (addresses #227).
In addition, cancelling GPG signing key warming no longer results in control characters being displayed on the terminal (fixes #228).
Added --immediate to skip "Press Enter to initialize keys" prompt (addresses #230). When requested keys are missing, the first Keychain to acquire the lock will run ssh-add immediately instead of first requiring Enter. This is technically safe, but no longer Keychain's default behavior since it's sub-optimal for some user scenarios. This can be enabled persistently via the [agent] immediate = true ~/.keychainrc configuration option.
Corrected keychain man pager integration (addresses #231). Color will be enabled when less is specifically detected, and -R will be enabled when not the default. Otherwise, keychain man output will not have color sequences. This fixes man page output on several systems.
--quiet now passes OpenSSH's native -q option to ssh-add, suppressing successful Identity added reports while preserving passphrase prompts and errors (fixes #237).
Keychain 3.0.1 also includes an important change -- the scope of its GnuPG integration has been deliberately narrowed, for using gpg-agent as a drop-in replacement for ssh-agent has been removed (addresses #164).
While this may seem counterintuitive, this decision was made to improve security. When loading an encrypted SSH key with this feature enabled, ssh-add, invoked by Keychain, prompted for the key's original passphrase. If the key was not already present in GnuPG's private-key store, GnuPG then requested a new passphrase through Pinentry and stored a persistent copy. A user unfamiliar with this behavior who simply wanted to use gpg-agent in place of ssh-agent may not have understood why GnuPG was requesting another passphrase, and not realize that this new passphrase would be used to re-encrypt their private key in GnuPG's on-disk persistent key store, thus duplicating it.
Even more unfortunate, the GnuPG passphrase request for the re-encryption happens right after the user supplied a passphrase for decryption, not as a separate flow, adding to the potential confusion. It's very possible that the user might hit Enter and submit an empty passphrase for the second unexpected prompt, potentially leaving the imported GnuPG copy without passphrase protection on disk.
The conclusion I came to is that GnuPG's ssh-agent protocol compatibility functions are more of an SSH private key importer/bridge which exclusively uses GnuPG's own key store, rather than a drop-in replacement for ssh-agent -- so we shouldn't treat it as if it is a drop-in replacement. While I could instead have tried to smooth over the rough edges with GnuPG, I would be fighting against GnuPG's intended architecture too much, so it's best to simply define a clear boundary of what it makes sense to support and not support.
Keychain will continue to support gpgs:, gpge:, and gpga: for proving and warming native GPG signing and decryption capabilities. You can still use keychain wipe --gpg to flush its in-memory secret cache. This remains supported and does not remove persistent key material. Keychain will invoke GnuPG for those operations, but it will no longer start, configure, adopt, or otherwise manage the gpg-agent lifecycle. Consider Keychain an orchestrator of ssh-agent's lifecycle, and a helpful utility for GnuPG key warming, but no longer responsible for gpg-agent's lifecycle.
This gives Keychain a clear boundary:
- Keychain manages
ssh-agentand SSH keys. - Keychain supports warming native GPG signing and decryption capabilities.
- GnuPG remains responsible for
gpg-agent, Pinentry, configuration, and lifecycle. gpg-agentis not supported as a substitute forssh-agent.
Upgrading will not remove any SSH private keys that were previously imported into GnuPG's persistent key store. Users who previously enabled this behavior should review their GnuPG key storage separately. You can do this by looking in ~/.gnupg/sshcontrol for imported keygrips (40 character hex), and then looking for equivalent ~/.gnupg/private-keys-v1.d/<keygrip>.key files. If gpg-connect-agent 'KEYINFO --ssh-list --ssh-fpr=sha256' /bye lists any keys with "C" in the protection field, it means the key is not protected with a passphrase. It is recommended that you remove these keys via gpg-connect-agent "DELETE_KEY <keygrip>" /bye and then remove the corresponding entry in ~/.gnupg/sshcontrol, making sure you are not deleting any private keys that might be associated with a native OpenPGP key.
The following GnuPG-related changes were made:
- Removed the behavior previously selected by
--ssh-allow-gpgand--ssh-spawn-gpg. The command-line spellings remain accepted as deprecated, warning no-ops so existing scripts continue to run. Inherited GnuPG SSH sockets are ignored, and conventional SSH keys are handled only byssh-agent. - Left
gpg-agentstartup, configuration, pinentry, and lifecycle entirely to GnuPG. Keychain no longer launches or validates the daemon and no longer exposesgpg_argsorKEYCHAIN_GPG_AGENT_ARGS. - Changed bare
keychain wipeto clear SSH-agent identities only. Flushinggpg-agent's entire in-memory secret cache now requires an explicit--gpg; legacy--wipe allretains its original both-target behavior. No persistent key material is removed. - Separated GPG credential warm-up from SSH multi-terminal coordination. Keychain now performs each requested signing or decryption proof exactly once instead of using real signing operations as repeated status probes.
- Restored
--quickas a deterministic SSH-only compatibility shortcut. GPG key arguments are ignored under--quick; Keychain does not invoke GnuPG, while still establishing the SSH-agent environment. - Made
wipe --gpgauthoritative and idempotent. An absent agent is a successful no-op, while missing tooling, timeouts, transport failures, agent errors, and unconfirmed responses now fail with actionable diagnostics. - Made GPG credential warm-up authoritative. Signing and decryption failures now identify the affected key and operation, decryption stops immediately when its encryption proof cannot be prepared, and successful decryption is verified against the original plaintext.
First stable release of Keychain 3.
Keychain 3 is a ground-up Python 3 evolution of Daniel Robbins' long-running
SSH-agent orchestrator with native GPG credential support. It preserves
Keychain's single-file deployment model as a single-file, self-contained
keychain.pyz (see
Python Rationale), while replacing the historical
Bourne shell implementation with a tested, auditable Python package. It
requires Python 3.9 or newer and has no third-party runtime dependencies.
The 3.0.0 release incorporates the work delivered through all three public betas. Highlights include:
-
One coordinated agent experience across terminals and sessions. Keychain discovers, validates, starts, and reconnects to a long-running agent per user and host. Managed
ssh-agentsockets now live at stable, host-specific paths under~/.keychain/, avoiding fragile temporary socket directories. -
Coordinated multi-terminal initialization. When several shells discover missing keys at the same time, they cooperate instead of racing for a lock or displaying duplicate passphrase prompts. Any waiting terminal can take over an inaccessible prompt, and all participants are notified when key loading completes.
-
A modern interface with strong 2.x compatibility. The action-oriented command surface includes
add,agent,list,env,inspect,help, andman. Traditional Keychain 2.x invocations remain supported through an explicit compatibility layer; intentional differences are documented underkeychain man topic:compat. -
Broader SSH and GPG workflows. Keychain can load PKCS#11 providers for smartcards and hardware tokens, and explicitly warm GPG signing, encryption, and decryption credentials. Verification failures are reported instead of being mistaken for success.
-
Native macOS confirmation support. When
--confirmis used with a new Keychain-managedssh-agent, Keychain installs a private, confirmation-onlyosascripthelper and gives OpenSSH a zero-dependency, native macOS Allow/Deny dialog for each key use. It works entirely with facilities built into macOS, with no additional askpass package or graphical toolkit to install. Denial, cancellation, a missing desktop session, or helper failure all fail closed.--confirmand--no-guiare intentionally incompatible. -
Configuration, inspection, and embedded documentation. Persistent preferences may live in an optional
~/.keychainrc;keychain inspectexposes resolved runtime state in human-readable or JSON form; and the complete, versioned manual ships inside the zipapp with topic and option-level help (keychain manandkeychain man --list). -
Hardened state handling and testing. Agent sockets, pidfiles, locks, coordination state, and waiter endpoints are ownership- and permission- checked. The test suite covers modern and legacy CLI behavior, real SSH and GPG integration, multi-terminal coordination, and supported platform differences.
Changes since 3.0.0_beta3:
- Added zero-dependency, native macOS
--confirmdialog support (#222). - Made
--confirm --no-guifail explicitly. - Fixed an issue where
--quietsuppressed the prompt to press Enter to initialize keys (#223). - Expanded
inspectwith.keychainrcstatus, effective settings and their sources, runtime identity, and relevant environment state.inspect --jsonnow emits a versioned diagnostic report suitable for bug reports. - Completed a dedicated security hardening pass across runtime storage,
configuration, agent handling, and generated shell output. Keychain now
rejects unsafe ownership or permissions on
.keychainrcand runtime files, validates SSH endpoints before use, rechecks agent identity before stopping it, writes private state atomically, quotes exports for each target shell, and rejects unsafe control characters. - Reworked concurrent initialization around operating-system advisory locks. Lock ownership and liveness no longer depend on PID heuristics; locks are released automatically when the owning process exits, and abandoned activation handoffs are safely reconciled.
- Hardened the release pipeline with commit-pinned GitHub Actions, verified release artifacts, and automatically generated SHA256 checksums.
- Converted command timeouts, malformed agent arguments, and operating-system failures into concise user errors instead of Python tracebacks (#224).
- Corrected askpass environment handling to follow OpenSSH's
DISPLAY,WAYLAND_DISPLAY, andSSH_ASKPASS_REQUIRE=forcerules. - Strengthened GPG warm-up verification so
gpga:proves both signing and decryption capability before reporting success, and made decrypt verification portable by avoiding/dev/nullas the temporary encrypted payload. - Expanded end-to-end SSH confirmation and agent startup coverage.
- Shortened managed
ssh-agentsocket names to avoid UNIX-domain socket path limits on macOS, Linux, and other POSIX systems. - Extensive code cleanups throughout the codebase (removing deprecated code, simplifying logic where possible, etc.)
Prior Keychain 3 beta users on MacOS will need to restart ssh-agent and Keychain completely in order to use the new --confirm functionality. Run
keychain agent stop once, then start Keychain normally. This will initialize the new graphical --confirm support. You will now be prompted with a graphical dialog to allow or deny each use of the cached key.
Third public beta of Keychain 3.x, collecting changes made after the
3.0.0_beta2 tag.
This release focuses on feature additions and robustness. It makes Keychain
more dependable during shell startup, easier to configure,
supports smartcards or other PKCS#11-backed SSH tokens, closes
a known .keychainrc documentation gap, and significantly enhances
the integrated documentation and documentation rendering.
Highlights:
-
More reliable agent startup. Keychain now keeps its managed
ssh-agentsocket in a stable location under~/.keychain/instead of depending on temporary/tmp/ssh-*paths. This helps avoid cases where the agent is still running but its socket directory has been cleaned up, a problem that showed up clearly under WSL but is not unique to it. -
Better smartcard and hardware-token support. You can now ask Keychain to load a PKCS#11 provider directly with
pkcs11:/path/to/provider.so. This is useful for SSH keys stored on smartcards, security keys, and similar devices. This addresses issue #216. -
Improved Documentation Formatting. Significant improvements in the embedded documentation renderer used by
keychain man. Pager support integrated. Supported .keychainrc config settings are now fully documented, streamlined and available. Addresses issue #217. -
Improved 2.9.8 compatibility details. A few legacy command-line edge cases with
--stopand--wipenow print a more accurate error message. -
Copyright has been updated to reflect assignment/ownership by Daniel Robbins, the person, removing reference to BreezyOps / Funtoo Solutions, Inc.
Second public beta of Keychain 3.x, collecting all changes made after the
3.0.0_beta1 tag.
This release transforms the multi-terminal experience and strengthens GPG key handling. The headline feature is a coordinated unlock protocol that eliminates the frustrating "could not acquire lock" errors when multiple shells start simultaneously -- a common occurrence when Visual Studio Code reconnects to WSL and restores several terminals at once.
Highlights:
-
Coordinated multi-terminal initialization (solves issue #214). Keychain now uses an elegant coordination protocol instead of the classic lock-timeout race. When multiple terminals detect missing SSH keys:
- All terminals display:
Press Enter to initialize keys - Pressing Enter in any terminal runs
ssh-addin that terminal - Other terminals wait automatically and are notified when initialization completes
- Waiting terminals print
Keys initialized by another terminal.and configure their environment without prompting
This eliminates the
could not acquire lockerrors that plagued earlier versions. The technical implementation uses a short-lived state lock for metadata updates, a dedicated activation lock to elect the loader, and FIFO endpoints for instant kernel-level notification (no polling). A takeover mechanism allows any waiting terminal to cancel a stuckssh-addby typingtakeover, ensuring you're never blocked by a hidden or inaccessible prompt. Internal coordination is quiet -- no moreWaiting N seconds for lock...messages during interactive key loading. - All terminals display:
-
Improved startup and key-loading output.
- Multi-key
ssh-addprompts render as compact lists instead of long inline messages - Common stale pidfile/socket cases (especially in WSL restart scenarios) are
folded into the
Starting ssh-agent...context instead of producing separate noisy notes - Empty
gpg-agentwipe diagnostics no longer render awkward(output: )text; non-actionable no-agent details are debug output - Successful remote initialization is reported as
Keys initialized by another terminal.
- Multi-key
-
Reliable GPG warm-up with explicit verification. The
gpge:KEYIDandgpga:KEYIDextended key syntax now perform a complete encrypt-then-decrypt verification cycle instead of relying on signing warm-up side effects. A tiny temporary payload is encrypted to the requested key and immediately decrypted throughgpg-agent. If this verification cannot be completed,addfails rather than reporting success. This is significantly more reliable across different GnuPG versions and key configurations, where signing warm-up may not populate the decryption passphrase cache. The legacygpgk:KEYIDalias remains equivalent togpgs:KEYID(signing warm-up only). -
Enhanced documentation. The embedded man page now includes comprehensive coverage of the coordination model (
keychain man topic:coordination), updated guidance for--lockwaitand--no-lockoptions, and clearer explanations of GPG warm-up guarantees. New design documents and a formal UX acceptance checklist support manual multi-terminal testing. -
Focused test coverage. New tests validate the coordination state file, waiter FIFO registration, activation lock handoff, takeover/cancel mechanics, and GPG end-to-end warm-up for both signing and encryption/decryption paths. Test infrastructure improvements ensure the checkout's source code is tested rather than any installed version, and CI coverage now includes macOS GPG validation.
Beta notes:
- 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. - Terminal prompt erasing is best-effort: used on ANSI-capable terminals,
falling back to ordinary line output when stderr is redirected,
TERM=dumb, or the prompt would wrap.
Initial public beta of Keychain 3.x.
Keychain 3 is a ground-up Python 3 rewrite of Daniel Robbins' long-running
SSH-agent manager with native GPG credential support. The release preserves
the traditional single-file deployment model through keychain.pyz, while
replacing the historical Bourne shell implementation with a tested, auditable
Python package.
Highlights:
- Ships as a standalone
keychain.pyzwith no third-party runtime dependencies. - Requires Python 3.9 or newer at runtime; the zipapp bootstrap can re-exec
into a newer
python3.NNon systems where/usr/bin/env python3is below the floor. - Adds an action-oriented command surface such as
keychain add,keychain agent start,keychain agent stop,keychain list,keychain env,keychain inspect,keychain help, andkeychain man. - Keeps keychain 2.x-style invocations working through an explicit compatibility layer.
- Embeds documentation in the zipapp; use
keychain manandkeychain man --listto browse it. - Uses a default-deny model for
KEYCHAIN_*environment variables; pass--allow-env/-Ewhen legacy environment-variable behavior is desired. - Releases under GPLv3 for the 3.x series. Keychain 2.x remains GPLv2.
Known beta notes:
- WSL login-shell startup can run keychain in a noninteractive/no-TTY context
when invoked by automation. This may fall through to
ssh_askpass; stale WSL/tmp/ssh-*sockets and hostname-specific pidfiles are tracked for follow-up polish.