FiloSottile/age · error
couldn't start plugin: %w
Error message
couldn't start plugin: %w
What it means
This error is returned by the plugin client's Wrap/WrapWithLabels function when it cannot open a connection to the plugin's helper binary via openClientConnection for the 'recipient-v1' protocol. The underlying cause (exec failure, handshake failure, etc.) is wrapped with %w. It means the age plugin process could not be launched or the client could not speak the v1 recipient protocol with it.
Source
Thrown at plugin/client.go:80
}
return r.encoding
}
func (r *Recipient) Wrap(fileKey []byte) (stanzas []*age.Stanza, err error) {
stanzas, _, err = r.WrapWithLabels(fileKey)
return
}
func (r *Recipient) WrapWithLabels(fileKey []byte) (stanzas []*age.Stanza, labels []string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("%s plugin: %w", r.name, err)
}
}()
conn, err := openClientConnection(r.name, "recipient-v1")
if err != nil {
return nil, nil, fmt.Errorf("couldn't start plugin: %w", err)
}
defer conn.Close()
// Phase 1: client sends recipient or identity and file key
addType := "add-recipient"
if r.identity {
addType = "add-identity"
}
if err := writeStanza(conn, addType, r.encoding); err != nil {
return nil, nil, err
}
if _, err := writeGrease(conn); err != nil {
return nil, nil, err
}
if err := writeStanzaWithBody(conn, "wrap-file-key", fileKey); err != nil {
return nil, nil, err
}
if err := writeStanza(conn, "extension-labels"); err != nil {View on GitHub (pinned to b74dce4cdb)
Solutions
- Install the plugin binary matching the recipient prefix and ensure it is on PATH and executable.
- Test the plugin manually (run its CLI) to confirm it starts.
- Verify the recipient string prefix maps to the correct plugin name (age1yubikey1... -> age-plugin-yubikey).
- Inspect the wrapped %w error for the OS-level cause (exec: not found, permission denied, exec format error) and fix accordingly.
- If the plugin needs hardware/agent access, ensure the device or agent is available before encrypting.
Example fix
// before
recipient, _ := age.ParseRecipients("age1yubikey1q...") // plugin not installed
out, err := age.Wrap(...) // fails: couldn't start plugin: exec not found
// after
if _, err := exec.LookPath("age-plugin-yubikey"); err != nil {
return fmt.Errorf("install age-plugin-yubikey first: %w", err)
}
out, err := age.Wrap(...) Defensive patterns
Strategy: try-catch
Validate before calling
// Go: check plugin availability before encryption
if _, err := exec.LookPath("age-plugin-yubikey"); err != nil {
return fmt.Errorf("plugin not installed or not on PATH: %w", err)
} Try / catch
stanzas, labels, err := client.WrapWithLabels(fileKey)
if err != nil {
var execErr *exec.Error
if errors.As(err, &execErr) {
return fmt.Errorf("plugin %q not installed: %w", execErr.Name, err)
}
return err
} Prevention
- Install the plugin binary and verify it is on PATH before encrypting.
- Check exec.LookPath at application startup and fail fast with a clear message.
- Pin plugin versions alongside your age library version.
- Document plugin prerequisites (PATH, hardware/agent) for deployment environments like CI.
When it happens
Trigger: Calling WrapWithLabels/Wrap (via age.Encrypt with a plugin recipient like age1yubikey1...) when the plugin binary named by the recipient prefix is not installed, not on PATH, not executable, or exits before completing the 'recipient-v1' handshake.
Common situations: Plugin not installed (e.g. age-plugin-yubikey missing from PATH); wrong recipient prefix so the client execs a nonexistent binary; plugin binary lacks execute permission; plugin built for another platform; plugin crashes at startup because hardware/agent is missing (YubiKey unplugged, yubikey-agent not running).
Related errors
- %s plugin: %w
- malformed recipient stanza: unexpected argument count
- malformed recipient stanza: invalid index
- malformed recipient stanza: unexpected index
- repeated labels stanza
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/23b7abb5319d060d.
Report an issue: GitHub.