FiloSottile/age · error

%s plugin: %w

Error message

%s plugin: %w

What it means

This is the plugin error wrapper in plugin.Recipient.WrapWithLabels: when any step of the age-plugin protocol fails, the returned error is prefixed with "<plugin-name> plugin: " so users can tell which plugin failed. It is not itself a failure condition; it decorates the underlying cause (couldn't start plugin, protocol error, plugin-reported failure) from the recipient-v1 wrap interaction.

Source

Thrown at plugin/client.go:74

// String returns the recipient encoding string ("age1name1...") or
// "<identity-based recipient>" if r was created by [Identity.Recipient].
func (r *Recipient) String() string {
	if r.identity {
		return "<identity-based recipient>"
	}
	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 {

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Check the wrapped error after the prefix: 'couldn't start plugin' means install/fix the age-plugin-<name> binary on PATH.
  2. Verify the plugin binary is executable and its version matches the age library/plugin protocol you use.
  3. Validate the recipient string; re-export it from the source plugin if the encoding is corrupt.
  4. Run the plugin binary directly with the same recipient to see its native error output and debug its configuration.
  5. Update both age and the plugin to compatible versions if the protocol handshake fails.
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the plugin is available before wrapping
name := recipient.Name()
if _, err := exec.LookPath("age-plugin-" + name); err != nil {
    return fmt.Errorf("plugin binary age-plugin-%s not found on PATH", name)
}

Try / catch

stanzas, err := recipient.Wrap(fileKey)
if err != nil {
    // err looks like: "<name> plugin: <cause>"
    msg := err.Error()
    if i := strings.Index(msg, " plugin: "); i >= 0 {
        cause := msg[i+len(" plugin: "):]
        log.Printf("plugin failure cause: %s", cause)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Wrap or WrapWithLabels on a plugin Recipient when: the plugin binary age-plugin-<name> is not found or fails to start; the plugin exits early or speaks a malformed protocol; the plugin reports an error stanza during wrap; any connection or stanza write fails.

Common situations: Plugin not installed or not on PATH; wrong plugin binary version (mismatched plugin protocol); a plugin that crashes on the given recipient encoding; corrupted or unsupported recipient string; container/CI environments missing the plugin binary.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/ec7ba0c59e5bb4b3. Report an issue: GitHub.