FiloSottile/age · error

not a plugin recipient

Error message

not a plugin recipient: %v

What it means

If the string decodes as bech32 but its human-readable part does not start with 'age1', it is not an age/plugin recipient. ParseRecipient rejects it so callers do not misinterpret unrelated bech32 strings (e.g. nostr npub, lightning invoices) as plugin recipients.

Solutions

  1. Check the string starts with 'age1' before calling ParseRecipient.
  2. If it starts with AGE-PLUGIN- or AGE-SECRET-KEY-, use the identity parser or native identity handling instead.
  3. Ask the user for the recipient (age1...) not the identity.
  4. Route parsing by prefix in dispatch code.

Example fix

// before
name, data, err := plugin.ParseRecipient(idString)
// after
if strings.HasPrefix(idString, "AGE-") { /* it's an identity, not a recipient */ }
name, data, err := plugin.ParseRecipient("age1frood1qqq...")
Defensive patterns

Strategy: validation

Validate before calling

if !strings.HasPrefix(s, "age1") { return fmt.Errorf("%q is not an age recipient", s) }

Type guard

func isRecipientString(s string) bool { return strings.HasPrefix(strings.TrimSpace(s), "age1") }

Try / catch

name, data, err := plugin.ParseRecipient(s)
if err != nil {
	if strings.Contains(err.Error(), "not a plugin recipient") { return fmt.Errorf("expected age1... recipient, got %q", s) }
	return err
}

Prevention

When it happens

Trigger: ParseRecipient called with a bech32 string like 'npub1...', 'lnbc1...', 'AGE-PLUGIN-...-1...' (identity, not recipient), or 'AGE-SECRET-KEY-...'.

Common situations: Wrong variable passed (identity instead of recipient); integrating other bech32 ecosystems and mixing strings; users pasting plugin identity lines where a recipient was requested.

Related errors


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

Appendix: source

Thrown at plugin/encode.go:63

// EncodeRecipient encodes a plugin recipient string for a plugin with the given
// name. If the name is invalid, it returns an empty string.
func EncodeRecipient(name string, data []byte) string {
	if !validPluginName(name) {
		return ""
	}
	s, _ := bech32.Encode("age1"+strings.ToLower(name), data)
	return s
}

// ParseRecipient decodes a plugin recipient string. It returns the plugin name
// in lowercase and the encoded data.
func ParseRecipient(s string) (name string, data []byte, err error) {
	hrp, data, err := bech32.Decode(s)
	if err != nil {
		return "", nil, fmt.Errorf("invalid recipient encoding: %v", err)
	}
	if !strings.HasPrefix(hrp, "age1") {
		return "", nil, fmt.Errorf("not a plugin recipient: %v", err)
	}
	name = strings.TrimPrefix(hrp, "age1")
	if !validPluginName(name) {
		return "", nil, fmt.Errorf("invalid plugin name: %q", name)
	}
	return name, data, nil
}

func validPluginName(name string) bool {
	if name == "" {
		return false
	}
	allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-._"
	for _, r := range name {
		if !strings.ContainsRune(allowed, r) {
			return false
		}
	}

View on GitHub (pinned to b74dce4cdb)