FiloSottile/age · error
invalid recipient encoding: %v
Error message
invalid recipient encoding: %v
What it means
ParseRecipient first decodes the input with bech32; if bech32 decoding fails (bad charset, checksum, mixed case, or length), it wraps the bech32 error as 'invalid recipient encoding'. The string was not decodable at all, so it cannot be a plugin recipient.
Source
Thrown at plugin/encode.go:60
return name, data, nil
}
// 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) {View on GitHub (pinned to b74dce4cdb)
Solutions
- Trim whitespace and re-copy the exact recipient string; verify its bech32 checksum.
- Use plugin.EncodeRecipient to regenerate the string from known data.
- Confirm the input is an age/plugin recipient and not another key format (ssh, PGP).
- If dispatching in RecipientV1, return a clear error to the CLI caller with the offending string.
Example fix
// before
name, data, err := plugin.ParseRecipient(strings.TrimSpace(raw))
// after
s := strings.TrimSpace(raw)
if s == "" || !strings.HasPrefix(s, "age1") { return fmt.Errorf("not an age recipient: %q", s) }
name, data, err := plugin.ParseRecipient(s) Defensive patterns
Strategy: validation
Validate before calling
s := strings.TrimSpace(raw)
if s == "" { return errors.New("empty recipient") }
if _, _, err := bech32.Decode(s); err != nil { return fmt.Errorf("not valid bech32: %w", err) } Type guard
func isBech32(s string) bool { _, _, err := bech32.Decode(strings.TrimSpace(s)); return err == nil } Try / catch
name, data, err := plugin.ParseRecipient(raw)
if err != nil {
return fmt.Errorf("recipient %q: %w", raw, err)
} Prevention
- Trim whitespace/newlines from pasted strings
- Copy strings verbatim without re-typing
- Store recipients in files with LF endings and no quoting
When it happens
Trigger: plugin.ParseRecipient (or plugin.NewRecipient / RecipientV1 dispatch) called with a string that fails bech32.Decode: typo, whitespace, missing/extra characters, corrupted checksum, or a non-bech32 string entirely.
Common situations: Copying recipient strings with surrounding quotes or trailing newlines; hand-typing long strings; storing recipients in files that got mangled (CRLF, truncation); passing an ssh key or email instead of an age recipient.
Related errors
- not a plugin identity: %v
- not a plugin recipient: %v
- malformed recipient %q: %v
- malformed confirm stanza: invalid YES option encoding
- malformed confirm stanza: invalid NO option encoding
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/5ff21e42c5d300e7.
Report an issue: GitHub.