m1k1o/neko · error

plugin '%s' already added

Error message

plugin '%s' already added

What it means

Returned by plugins.Manager's dependency graph (addPlugin) when a plugin with the same name is registered twice and the existing dependency entry already holds a plugin instance. The registry keys plugins by plugin.Name(), so names must be unique; a duplicate would silently overwrite the first registration, so it is rejected.

Source

Thrown at server/internal/plugins/dependency.go:71

	}

	a.logger.Info().Str("plugin", a.plugin.Name()).Msg("plugin started")
	return nil
}

type dependiencies struct {
	deps   map[string]*dependency
	logger zerolog.Logger
}

func (d *dependiencies) addPlugin(plugin types.Plugin) error {
	pluginName := plugin.Name()

	plug, ok := d.deps[pluginName]
	if !ok {
		plug = &dependency{}
	} else if plug.plugin != nil {
		return fmt.Errorf("plugin '%s' already added", pluginName)
	}

	plug.plugin = plugin
	plug.logger = d.logger
	d.deps[pluginName] = plug

	dplug, ok := plugin.(types.DependablePlugin)
	if !ok {
		return nil
	}

	for _, depName := range dplug.DependsOn() {
		dependsOn, ok := d.deps[depName]
		if !ok {
			dependsOn = &dependency{}
		} else if dependsOn.plugin != nil {
			// if there is a cyclical dependency, break it and return error
			if tdep, ok := dependsOn.findPlugin(pluginName); ok {

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Give each plugin a unique value from Name() (e.g. include a namespace or version)
  2. Check the load/init flow for duplicate registration of the same plugin and load it only once
  3. If reloading, remove the old dependency entry (delete from d.deps) before calling addPlugin again
  4. Wrap addPlugin in an errors.Is check for this message and log which plugin name collided

Example fix

// before
func (p *myPlugin) Name() string { return "filetransfer" }
// two copies loaded -> "plugin 'filetransfer' already added"

// after
func (p *myPlugin) Name() string {
    if p.variant == "beta" { return "filetransfer-beta" }
    return "filetransfer"
}
Defensive patterns

Strategy: validation

Validate before calling

if _, ok := manager.plugins.findPlugin(p.Name()); ok {
    return fmt.Errorf("plugin %q already registered", p.Name())
}
return manager.plugins.addPlugin(p)

Try / catch

if err := manager.plugins.addPlugin(p); err != nil {
    if strings.Contains(err.Error(), "already added") {
        log.Warn().Str("plugin", p.Name()).Msg("plugin already registered, skipping")
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling manager.plugins.addPlugin(p) (directly or via Manager.load) twice with two plugin objects whose Name() returns the same string, while the earlier plugin was never removed (no cyclical-break/cleanup deleted its entry).

Common situations: Registering the same plugin binary/so file twice in config; two builds of one plugin both reporting the same Name(); a plugin reload path that forgot to delete the old entry from d.deps; embedding a plugin in two bundle plugins that are both loaded.

Related errors


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