FiloSottile/age · error
malformed recipient %q: %v
Error message
malformed recipient %q: %v
What it means
tag.ParseRecipient decodes a Bech32 public key string with prefix "age1tag1" (or "age1tagpq1") using plugin.ParseRecipient. This error wraps a failure of that low-level parsing step — the string is not valid Bech32, has the wrong HRP/prefix, or fails plugin-level parsing — so no Recipient can be constructed from it.
Source
Thrown at tag/tag.go:46
)
// Recipient is a tagged P-256 or hybrid P-256 + ML-KEM-768 recipient.
//
// The latter recipient is safe against future cryptographically-relevant
// quantum computers, and can only be used along with other post-quantum
// recipients.
type Recipient struct {
pk hpke.PublicKey
}
var _ age.Recipient = &Recipient{}
// ParseRecipient returns a new [Recipient] from a Bech32 public key
// encoding with the "age1tag1" or "age1tagpq1" prefix.
func ParseRecipient(s string) (*Recipient, error) {
t, k, err := plugin.ParseRecipient(s)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
switch t {
case "tag":
r, err := NewClassicRecipient(k)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
case "tagpq":
r, err := NewHybridRecipient(k)
if err != nil {
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
default:
return nil, fmt.Errorf("malformed recipient %q: invalid type %q", s, t)
}
}View on GitHub (pinned to b74dce4cdb)
Solutions
- Check the wrapped %v for the exact Bech32/plugin parse failure (bad checksum, bad HRP, bad character).
- Verify the string starts with the correct prefix (age1tag1 for classic, age1tagpq1 for hybrid) and is unmodified.
- Regenerate or re-export the recipient string from the key holder rather than retyping it.
- Strip whitespace/newlines and keep the original casing before parsing.
Example fix
// before
r, err := tag.ParseRecipient(strings.TrimSpace(userInput) + "")
// after
s := strings.TrimSpace(userInput)
if !strings.HasPrefix(s, "age1tag1") && !strings.HasPrefix(s, "age1tagpq1") {
return fmt.Errorf("not a tag recipient: %s", s)
}
r, err := tag.ParseRecipient(s) Defensive patterns
Strategy: validation
Validate before calling
func looksLikeTagRecipient(s string) bool {
s = strings.TrimSpace(s)
return strings.HasPrefix(s, "age1tag1") || strings.HasPrefix(s, "age1tagpq1")
}
if !looksLikeTagRecipient(userInput) {
return errors.New("not a tag recipient")
}
r, err := tag.ParseRecipient(userInput) Prevention
- Validate the age1tag1/age1tagpq1 prefix before parsing.
- Trim whitespace and reject hand-edited recipient strings.
- Copy recipient strings verbatim from the key holder's export output.
When it happens
Trigger: Calling tag.ParseRecipient(s) where s fails plugin.ParseRecipient: invalid Bech32 checksum, wrong HRP (not the expected age/tag prefix), mixed case, or invalid characters.
Common situations: Users pasting an X25519 age1... key instead of a tag recipient; truncated or hand-edited recipient strings; copy/paste introducing whitespace or case changes; confusing age1tag1 with age1tagpq1 keys.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- not a plugin identity: %v
- invalid recipient encoding: %v
- not a plugin recipient: %v
- malformed recipient %q: invalid type %q
- malformed SSH recipient: %q: %v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/c16f94854a7b3ab7.
Report an issue: GitHub.