FiloSottile/age · error
malformed recipient %q: invalid type %q
Error message
malformed recipient %q: invalid type %q
What it means
tag.ParseRecipient decodes the Bech32 string into a plugin type t ("tag" or "tagpq"). If the decoded type is neither, this error reports the unexpected type. It means the string parsed as a valid plugin recipient but for a different recipient type than this library supports.
Source
Thrown at tag/tag.go:62
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)
}
}
const compressedPointSize = 1 + 32
const uncompressedPointSize = 1 + 32 + 32
// NewClassicRecipient returns a new P-256 [Recipient] from a raw public key.
func NewClassicRecipient(publicKey []byte) (*Recipient, error) {
if len(publicKey) != compressedPointSize {
return nil, fmt.Errorf("invalid tag recipient public key size %d", len(publicKey))
}
p, err := nistec.NewP256Point().SetBytes(publicKey)
if err != nil {
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
}
k, err := hpke.DHKEM(ecdh.P256()).NewPublicKey(p.Bytes())
if err != nil {
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)View on GitHub (pinned to b74dce4cdb)
Solutions
- Check the reported %q type to identify what kind of recipient you actually passed.
- Use plugin.Parse recipients (e.g. age.ParseRecipient) for standard X25519 age1... keys instead of tag.ParseRecipient.
- Ensure you have the correct tag recipient string for this library (age1tag1 / age1tagpq1).
- If you need both, dispatch on the parsed type before choosing the constructor.
Example fix
// before
r, err := tag.ParseRecipient("age1qq...") // X25519 recipient
// after
if strings.HasPrefix(s, "age1tag1") || strings.HasPrefix(s, "age1tagpq1") {
r, err = tag.ParseRecipient(s)
} else {
r, err = age.ParseRecipients(...) // handle standard age keys separately
} Defensive patterns
Strategy: validation
Validate before calling
s := strings.TrimSpace(userInput)
switch {
case strings.HasPrefix(s, "age1tag1"), strings.HasPrefix(s, "age1tagpq1"):
r, err = tag.ParseRecipient(s)
default:
return fmt.Errorf("unsupported recipient type, expected age1tag1/age1tagpq1")
} Type guard
func isTagRecipient(s string) bool {
return strings.HasPrefix(s, "age1tag1") || strings.HasPrefix(s, "age1tagpq1")
} Prevention
- Dispatch on the recipient prefix before choosing a parser.
- Use age.ParseRecipients for standard X25519 keys, tag.ParseRecipient only for tag keys.
- Never assume all age1... strings are interchangeable.
When it happens
Trigger: Calling tag.ParseRecipient with a valid plugin.Bech32 recipient string whose inner type is not "tag" or "tagpq" — e.g. an X25519 "age1..." key or another plugin's recipient.
Common situations: Passing a standard age X25519 recipient to tag.ParseRecipient; passing recipients from other age plugins; typos in the recipient prefix that still decode as a valid but different type.
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
- malformed recipient %q: %v
- malformed SSH recipient: %q: %v
- not a plugin identity: %v
- invalid recipient encoding: %v
- not a plugin recipient: %v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/31f7677feb09ac87.
Report an issue: GitHub.