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
- 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).
- Make the plugin implement types.ExposablePlugin by adding an ExposeService() method with the required signature.
- 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
- Maintain a compile-time assertion in each plugin: `var _ types.ExposablePlugin = (*MyPlugin)(nil)`.
- Document which plugins expose services and only call LookupService for those.
- After refactoring a plugin, re-run interface assertions/compilation before deploying.
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
- image data not found
- screencast not enabled
- unable to get first image
- timeouted while waiting for first image
- listener cannot be nil
AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01).
Data as JSON: /api/errors/294660b15f6929ca.
Report an issue: GitHub.