m1k1o/neko · error

plugin '%s' is not exposable

Error message

plugin '%s' is not exposable

What it means

LookupService returns a plugin's exposed service, but only plugins implementing the types.ExposablePlugin interface can be exposed. When the named plugin exists but its implementation does not satisfy that interface, the manager rejects the lookup with this error. It is a capability mismatch, not a missing-plugin condition.

Source

Thrown at server/internal/plugins/manager.go:156

func (manager *ManagerCtx) Shutdown() error {
	_ = manager.plugins.forEach(func(d *dependency) error {
		err := d.plugin.Shutdown()
		manager.logger.Err(err).Str("plugin", d.plugin.Name()).Msg("plugin shutdown")
		return nil
	})
	return nil
}

func (manager *ManagerCtx) LookupService(pluginName string) (any, error) {
	plug, ok := manager.plugins.findPlugin(pluginName)
	if !ok {
		return nil, fmt.Errorf("plugin '%s' not found", pluginName)
	}

	expPlug, ok := plug.plugin.(types.ExposablePlugin)
	if !ok {
		return nil, fmt.Errorf("plugin '%s' is not exposable", pluginName)
	}

	return expPlug.ExposeService(), nil
}

func (manager *ManagerCtx) Metadata() []types.PluginMetadata {
	var plugins []types.PluginMetadata

	_ = manager.plugins.forEach(func(d *dependency) error {
		dependsOn := make([]string, 0)
		deps, isDependalbe := d.plugin.(types.DependablePlugin)
		if isDependalbe {
			dependsOn = deps.DependsOn()
		}

		_, isExposable := d.plugin.(types.ExposablePlugin)

		plugins = append(plugins, types.PluginMetadata{

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Verify the plugin name passed to LookupService actually refers to a plugin that exposes a service (grep the plugin registry for which plugins implement ExposablePlugin).
  2. Make the plugin implement types.ExposablePlugin by adding an ExposeService() method with the required signature.
  3. If the plugin intentionally has no service, stop calling LookupService for it and interact via its normal plugin interface instead.

Example fix

// before
func (p *MyPlugin) Start() error { ... } // no ExposeService

// after
func (p *MyPlugin) ExposeService() interface{} {
    return p.service // implement types.ExposablePlugin
}
Defensive patterns

Strategy: type-guard

Validate before calling

plug, err := registry.Get(pluginName)
if err != nil { return err }
if _, ok := plug.(types.ExposablePlugin); !ok {
    return fmt.Errorf("plugin %q does not expose a service", pluginName)
}

Type guard

func asExposable(p types.Plugin) (types.ExposablePlugin, bool) {
    ep, ok := p.(types.ExposablePlugin)
    return ep, ok
}

Try / catch

svc, err := manager.LookupService(pluginName)
if err != nil {
    if strings.Contains(err.Error(), "is not exposable") {
        // fall back to non-service plugin interaction
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling Manager.LookupService(pluginName) for a plugin that is registered but whose main type does not implement types.ExposablePlugin (i.e., the type assertion plug.plugin.(types.ExposablePlugin) fails at server/internal/plugins/manager.go:156).

Common situations: Requesting a service from a plugin that only implements basic lifecycle (Load/Start/Stop) such as an audio/device plugin; misconfiguring the client to look up a plugin by the wrong name so a non-exposable plugin is matched; after refactoring a plugin and accidentally dropping the ExposeService method so it no longer satisfies the interface.

Related errors


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