FiloSottile/age · error

invalid plugin name: %q

Error message

invalid plugin name: %q

What it means

plugin.New creates a Plugin that will be invoked as age-plugin-<name>; the name is validated with validPluginName and stored lowercased. An empty or grammatically invalid name is rejected immediately because the plugin protocol could not address the binary.

Source

Thrown at plugin/plugin.go:53

	recipient     func([]byte) (age.Recipient, error)
	idAsRecipient func([]byte) (age.Recipient, error)
	identity      func([]byte) (age.Identity, error)

	stdin          io.Reader
	stdout, stderr io.Writer

	sr *format.StanzaReader
	// broken is set if the protocol broke down during an interaction function
	// called by a Recipient or Identity.
	broken bool
}

// New creates a new Plugin with the given case-insensitive name.
//
// For example, a plugin named "frood" would be invoked as "age-plugin-frood".
func New(name string) (*Plugin, error) {
	if !validPluginName(name) {
		return nil, fmt.Errorf("invalid plugin name: %q", name)
	}
	return &Plugin{name: strings.ToLower(name), stdin: os.Stdin,
		stdout: os.Stdout, stderr: os.Stderr}, nil
}

// Name returns the name of the plugin.
func (p *Plugin) Name() string {
	return p.name
}

// RegisterFlags registers the plugin's flags with the given [flag.FlagSet], or
// with the default [flag.CommandLine] if fs is nil. It must be called before
// [flag.Parse] and [Plugin.Main].
//
// This allows the plugin to expose additional flags when invoked manually, for
// example to implement a keygen mode.
func (p *Plugin) RegisterFlags(fs *flag.FlagSet) {
	if fs == nil {

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Pass only the bare plugin name (e.g. "frood"), not age-plugin-frood or a path.
  2. Normalize with strings.ToLower and strip forbidden characters before calling.
  3. Validate with the same rules as validPluginName (lowercase alphanumeric and '-' separators) in config loading.
  4. Trim whitespace from configuration values.

Example fix

// before
p, err := plugin.New("/usr/local/bin/age-plugin-frood")
// after
p, err := plugin.New("frood")
Defensive patterns

Strategy: validation

Validate before calling

name = strings.ToLower(strings.TrimSpace(name))
if name == "" { return errors.New("plugin name is empty") }
if strings.HasPrefix(name, "age-plugin-") { name = strings.TrimPrefix(name, "age-plugin-") }

Type guard

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

Try / catch

p, err := plugin.New(name)
if err != nil {
	return fmt.Errorf("cannot start plugin %q: %w", name, err)
}

Prevention

When it happens

Trigger: plugin.New("") or plugin.New("My_Plugin") / any name with characters outside validPluginName's set; passing a full binary name like "age-plugin-frood" or a path instead of just the name.

Common situations: Deriving the name from os.Args[0] or a full executable path instead of the bare plugin name; user-supplied config with mixed case/underscores; forgetting the prefix must be stripped.

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/099423eadde8ace2. Report an issue: GitHub.