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

  1. Check the reported %q type to identify what kind of recipient you actually passed.
  2. Use plugin.Parse recipients (e.g. age.ParseRecipient) for standard X25519 age1... keys instead of tag.ParseRecipient.
  3. Ensure you have the correct tag recipient string for this library (age1tag1 / age1tagpq1).
  4. 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

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

Related errors


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