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

  1. Trim whitespace and re-copy the exact recipient string; verify its bech32 checksum.
  2. Use plugin.EncodeRecipient to regenerate the string from known data.
  3. Confirm the input is an age/plugin recipient and not another key format (ssh, PGP).
  4. 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

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


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