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

  1. Rebuild the plugin against the exact server version's types.Plugin interface (same module version) and reload
  2. Verify the plugin implements every method of types.Plugin (Name, Start, Shutdown, ...) on the exported type
  3. Confirm the plugin file is the intended artifact for this product, not another plugin binary
  4. 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

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


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/794095ce6334d22c. Report an issue: GitHub.