FiloSottile/age · error

invalid plugin name: %q

Error message

invalid plugin name: %q

What it means

After decoding and validating the AGE-PLUGIN- prefix, ParseIdentity extracts the plugin name and validates it with validPluginName. If the extracted name is empty or contains characters outside the allowed set, the library rejects it because the name must be usable to invoke age-plugin-<name> on PATH.

Source

Thrown at plugin/encode.go:40

	}
	s, _ := bech32.Encode("AGE-PLUGIN-"+strings.ToUpper(name)+"-", data)
	return s
}

// ParseIdentity decodes a plugin identity string. It returns the plugin name
// in lowercase and the encoded data.
func ParseIdentity(s string) (name string, data []byte, err error) {
	hrp, data, err := bech32.Decode(s)
	if err != nil {
		return "", nil, fmt.Errorf("invalid identity encoding: %v", err)
	}
	if !strings.HasPrefix(hrp, "AGE-PLUGIN-") || !strings.HasSuffix(hrp, "-") {
		return "", nil, fmt.Errorf("not a plugin identity: %v", err)
	}
	name = strings.TrimSuffix(strings.TrimPrefix(hrp, "AGE-PLUGIN-"), "-")
	name = strings.ToLower(name)
	if !validPluginName(name) {
		return "", nil, fmt.Errorf("invalid plugin name: %q", name)
	}
	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)

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Print the extracted name (strings between AGE-PLUGIN- and trailing -) and check it against validPluginName rules (lowercase alphanumeric with allowed separators).
  2. Fix the plugin identity by re-encoding it with plugin.EncodeIdentity using a valid name.
  3. Use the exact name of an installed age-plugin-<name> binary.
  4. Regenerate the identity if the original is corrupt.

Example fix

// before
id, err := plugin.ParseIdentity("AGE-PLUGIN-MY_PLUGIN-1QQQ...") // '_' invalid
// after
id, err := plugin.ParseIdentity("AGE-PLUGIN-MY-PLUGIN-1QQQ...")
Defensive patterns

Strategy: validation

Validate before calling

name := strings.TrimSuffix(strings.TrimPrefix(hrpGuess, "AGE-PLUGIN-"), "-")
if name == "" { reject() } // then validate charset

Type guard

func validName(s string) bool {
	if s == "" { return false }
	for _, r := range s {
		ok := r == '-' || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9')
		if !ok { return false }
	}
	return true
}

Try / catch

name, data, err := plugin.ParseIdentity(s)
if err != nil {
	return fmt.Errorf("identity %q rejected: %w", s, err)
}

Prevention

When it happens

Trigger: ParseIdentity called with a string like 'AGE-PLUGIN--1QQ...' (empty name) or whose name contains invalid characters (uppercase beyond case normalization, spaces, punctuation, non-ASCII) after lowercasing.

Common situations: Hand-crafted plugin strings from documentation typos; plugin binaries renamed with characters the name grammar forbids; truncated strings leaving an empty name; mixing separators like '_' instead of the allowed characters.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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