m1k1o/neko · error
not a valid plugin
Error message
not a valid plugin
What it means
Returned by Manager.load when a plugin binary/so file loads and its exported symbol resolves, but the resulting symbol does not implement the types.Plugin interface. load only accepts objects satisfying types.Plugin (Name, Start, Shutdown etc.); anything else is rejected with this error.
Source
Thrown at server/internal/plugins/manager.go:92
manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin")
return nil
})
}
func (manager *ManagerCtx) load(path string) error {
pl, err := plugin.Open(path)
if err != nil {
return err
}
sym, err := pl.Lookup("Plugin")
if err != nil {
return err
}
p, ok := sym.(types.Plugin)
if !ok {
return fmt.Errorf("not a valid plugin")
}
if err = manager.plugins.addPlugin(p); err != nil {
return fmt.Errorf("failed to add plugin: %w", err)
}
return nil
}
func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) {
_ = manager.plugins.forEach(func(d *dependency) error {
if err := d.plugin.Config().Init(cmd); err != nil {
log.Err(err).Str("plugin", d.plugin.Name()).Msg("unable to initialize configuration")
}
return nil
})
}
View on GitHub (pinned to b0f01cedea)
Solutions
- Rebuild the plugin against the exact server version's types.Plugin interface (same module version) and reload
- Verify the plugin implements every method of types.Plugin (Name, Start, Shutdown, ...) on the exported type
- Confirm the plugin file is the intended artifact for this product, not another plugin binary
- Check the exported symbol path/registry key matches what load expects
Example fix
// before
type myPlugin struct{}
func (p *myPlugin) Name() string { return "my" }
// missing Start/Shutdown -> "not a valid plugin"
// after
type myPlugin struct{}
func (p *myPlugin) Name() string { return "my" }
func (p *myPlugin) Start() error { return nil }
func (p *myPlugin) Shutdown() error { return nil }
var _ types.Plugin = (*myPlugin)(nil) // compile-time check Defensive patterns
Strategy: type-guard
Validate before calling
// build-time guarantee
var _ types.Plugin = (*myPlugin)(nil)
// load-time pre-check
sym, err := lookupSymbol(so)
if err != nil { return err }
if _, ok := sym.(types.Plugin); !ok {
return fmt.Errorf("artifact %s does not implement types.Plugin", soPath)
} Type guard
func asPlugin(sym any) (types.Plugin, bool) {
p, ok := sym.(types.Plugin)
return p, ok
} Try / catch
if err := manager.load(soPath); err != nil {
if err.Error() == "not a valid plugin" {
log.Error().Str("file", soPath).Msg("rebuild plugin against current server types.Plugin")
}
return err
} Prevention
- Compile plugins against the same module version as the server; pin versions together
- Add `var _ types.Plugin = (*Plugin)(nil)` in every plugin to fail at compile time
- Only place verified plugin artifacts in the plugin directory; checksum them in CI
When it happens
Trigger: Loading a .so whose exported entry symbol returns a value that is not a types.Plugin — e.g. a shared library built for a different product, a Go plugin compiled without the required interface methods, or a symbol type change after an API upgrade.
Common situations: Mixing plugin .so files built against a different server version where types.Plugin's method set changed; loading an arbitrary .so by mistake in the plugin dir; a custom plugin that implements most but not all Plugin interface methods.
Related errors
- target stream manager does not support moving listeners
- plugin '%s' already added
- cyclical dependency detected: '%s' <-> '%s'
- unable to unmarshal %s plugin settings from global settings:
- unable to unmarshal %s plugin settings from profile: %w
AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01).
Data as JSON: /api/errors/794095ce6334d22c.
Report an issue: GitHub.