{"record":{"id":"099423eadde8ace2","repo":"FiloSottile/age","slug":"invalid-plugin-name-q-099423","errorCode":null,"errorMessage":"invalid plugin name: %q","messagePattern":"invalid plugin name: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/plugin.go","lineNumber":53,"sourceCode":"\trecipient     func([]byte) (age.Recipient, error)\n\tidAsRecipient func([]byte) (age.Recipient, error)\n\tidentity      func([]byte) (age.Identity, error)\n\n\tstdin          io.Reader\n\tstdout, stderr io.Writer\n\n\tsr *format.StanzaReader\n\t// broken is set if the protocol broke down during an interaction function\n\t// called by a Recipient or Identity.\n\tbroken bool\n}\n\n// New creates a new Plugin with the given case-insensitive name.\n//\n// For example, a plugin named \"frood\" would be invoked as \"age-plugin-frood\".\nfunc New(name string) (*Plugin, error) {\n\tif !validPluginName(name) {\n\t\treturn nil, fmt.Errorf(\"invalid plugin name: %q\", name)\n\t}\n\treturn &Plugin{name: strings.ToLower(name), stdin: os.Stdin,\n\t\tstdout: os.Stdout, stderr: os.Stderr}, nil\n}\n\n// Name returns the name of the plugin.\nfunc (p *Plugin) Name() string {\n\treturn p.name\n}\n\n// RegisterFlags registers the plugin's flags with the given [flag.FlagSet], or\n// with the default [flag.CommandLine] if fs is nil. It must be called before\n// [flag.Parse] and [Plugin.Main].\n//\n// This allows the plugin to expose additional flags when invoked manually, for\n// example to implement a keygen mode.\nfunc (p *Plugin) RegisterFlags(fs *flag.FlagSet) {\n\tif fs == nil {","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/plugin/plugin.go#L35-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass only the bare plugin name (e.g. \"frood\"), not age-plugin-frood or a path.","Normalize with strings.ToLower and strip forbidden characters before calling.","Validate with the same rules as validPluginName (lowercase alphanumeric and '-' separators) in config loading.","Trim whitespace from configuration values."],"exampleFix":"// before\np, err := plugin.New(\"/usr/local/bin/age-plugin-frood\")\n// after\np, err := plugin.New(\"frood\")","handlingStrategy":"validation","validationCode":"name = strings.ToLower(strings.TrimSpace(name))\nif name == \"\" { return errors.New(\"plugin name is empty\") }\nif strings.HasPrefix(name, \"age-plugin-\") { name = strings.TrimPrefix(name, \"age-plugin-\") }","typeGuard":"func usablePluginName(name string) bool {\n\tif name == \"\" { return false }\n\tfor _, r := range name {\n\t\tif r != '-' && (r < 'a' || r > 'z') && (r < '0' || r > '9') { return false }\n\t}\n\treturn true\n}","tryCatchPattern":"p, err := plugin.New(name)\nif err != nil {\n\treturn fmt.Errorf(\"cannot start plugin %q: %w\", name, err)\n}","preventionTips":["Pass the bare name, never the binary path or age-plugin- prefix","Normalize case/whitespace in config before plugin.New","Validate plugin names in config schemas"],"tags":["go","validation","naming","age-plugin"],"backgroundTag":"schema-validation-failed","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}