#3612·gopass

age: agent cannot cache passphrase-protected SSH identities

Author: ermengchaoCreated Sep 17, 2026Updated Sep 17, 2026

Summary

When an Age-backed password store uses an SSH recipient and the corresponding local identity is a passphrase-protected OpenSSH private key, the Age agent starts and is reachable, but the SSH identity is not transferred to it.

Every new gopass invocation therefore falls back to direct decryption and prompts for the SSH private key passphrase again.

The debug log shows that *agessh.EncryptedSSHIdentity is intentionally skipped because it is not serializable:

skipping non-serializable identity *agessh.EncryptedSSHIdentity for agent transfer

Issue #3507 already documents that SSH identities cannot currently be serialized and are skipped. This issue is specifically a feature request to support passphrase-protected SSH identities in the Age agent, or alternatively document this limitation explicitly.

Steps To Reproduce

Prerequisites:

  • An Age-backed password store.
  • A matching ssh-ed25519 recipient in .age-recipients.
  • A passphrase-protected OpenSSH private key at $HOME/.ssh/id_ed25519.
  • No native identity at $HOME/.config/gopass/age/identities.

Configure the Age backend:

fish
gopass config age.agent-enabled true
gopass config age.agent-timeout 7200
gopass config age.ssh-key-path "$HOME/.ssh"

Force terminal-based passphrase entry, stop any existing agent, and read the same secret twice:

fish
set -lx GOPASS_AGE_STDIN_PASSPHRASE true

gopass age agent stop
gopass --nosync show --password <secret> >/dev/null
gopass --nosync show --password <secret> >/dev/null

The SSH private key passphrase is requested on both invocations.

The agent itself reports that it is running:

Age agent is running

Expected behavior

The first access should request the SSH private key passphrase and load the resulting identity into the Age agent.

Subsequent invocations should use the cached identity without prompting again until age.agent-timeout expires or the agent is explicitly locked.

If caching SSH identities is intentionally unsupported for security or implementation reasons, the documentation should clarify that the Age agent only supports serializable native Age/plugin identities and does not cache SSH identities.

Actual behavior

The agent is running, but the SSH identity is skipped during transfer. Each invocation falls back to direct decryption and asks for the passphrase again.

Relevant sanitized debug output:

agent.(*Client).connect connected to agent at <RUNTIME_DIR>/gopass/gopass-age-agent.sock
age.(*Age).tryStartAgent age agent already running

age.(*Age).Identities reading native identities from <HOME>/.config/gopass/age/identities
age.(*Age).Identities failed to decrypt existing identities:
open <HOME>/.config/gopass/age/identities: no such file or directory

age.(*Age).getSSHIdentities using custom SSH key path <HOME>/.ssh
age.(*Age).getSSHIdentities returned 1 SSH Identities
age.(*Age).getAllIdentities got 1 ssh identities
age.IdentityToRecipient parsed age identity as encrypted SSHIdentity

age.(*Age).identitiesToString skipping non-serializable identity *agessh.EncryptedSSHIdentity for agent transfer

age.(*Age).Decrypt failed to decrypt with agent:
agent error: failed to decrypt: failed to decrypt: no identities specified
age.(*Age).Decrypt falling back to direct decryption

age.(*askPass).Passphrase Value for <HOME>/.ssh/id_ed25519.pub not found in cache
age.(*askPass).getPassphrase GOPASS_AGE_STDIN_PASSPHRASE is set, using CLI fallback

The direct-decryption path reports that the passphrase was cached:

age.(*askPass).Passphrase Updated value for <HOME>/.ssh/id_ed25519.pub in cache

However, this cache is local to the short-lived gopass process. The next invocation starts with an empty cache and prompts again.

Environment

  • OS: Arch Linux
  • Architecture: ARM64
  • Shell: fish
  • gopass version:
gopass 1.17.0 go1.27.0-X:nodwarf5 linux arm64
  • Installation method: Arch Linux package (pacman -S gopass)
  • Crypto backend: Age
  • Identity type: passphrase-protected OpenSSH Ed25519 private key
  • age.agent-enabled: true
  • age.agent-timeout: 7200
  • age.ssh-key-path: <HOME>/.ssh

Additional context

This appears distinct from #3467 and its fix in #3488. That fix recovers an agent that is running without identities, but in this case recovery still cannot populate the agent because the only available identity is an *agessh.EncryptedSSHIdentity, which is skipped during serialization.

It is also related to #3507, whose description explicitly states that SSH identities cannot be serialized and should be skipped. The framing fix from that issue correctly handles multiple serializable identities, but it does not enable SSH identity caching.

Current workarounds include:

  • providing the passphrase through GOPASS_AGE_PASSWORD;
  • using age.usekeychain with an available OS keychain/Secret Service;
  • entering the passphrase for every new gopass process;
  • using a native Age identity instead of an SSH identity.

Would supporting passphrase-protected SSH identities in the Age agent be within the intended scope of the agent? If not, documenting the limitation would help users understand why a running agent does not prevent repeated SSH key passphrase prompts.