m1k1o/neko · error

failed to add plugin: %w

Error message

failed to add plugin: %w

What it means

Returned by Manager.load when manager.plugins.addPlugin(p) fails; load wraps the underlying error (duplicate plugin — error 60, or cyclical dependency — error 61) with this message. It is a wrapper: the real cause is always in the wrapped %w chain.

Source

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

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
	})
}

func (manager *ManagerCtx) SetConfigs() {
	_ = manager.plugins.forEach(func(d *dependency) error {
		d.plugin.Config().Set()
		return nil

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Inspect the wrapped error chain (errors.Unwrap / %v of the error) to find whether it is 'already added' or 'cyclical dependency'
  2. For duplicates: assign a unique Name() per plugin or remove the earlier registration before loading
  3. For cycles: restructure plugin dependencies so they form a DAG (see error 61)
  4. If reloading, delete the old entry from d.deps before calling load again

Example fix

// before
err := manager.load(so)
// plugin 'x' loaded twice -> failed to add plugin: plugin 'x' already added

// after
if _, ok := manager.plugins.findPlugin(p.Name()); ok {
    return nil // already loaded, skip
}
err := manager.load(so)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, ok := manager.plugins.findPlugin(name); ok {
    return fmt.Errorf("skip load: %s already registered", name)
}
// and pre-check dependency graph for cycles before load
if createsCycle(manager.plugins.deps, p) {
    return fmt.Errorf("refusing to load %s: dependency cycle", p.Name())
}

Try / catch

if err := manager.load(soPath); err != nil {
    var root error = err
    for errors.Unwrap(root) != nil { root = errors.Unwrap(root) }
    switch {
    case strings.Contains(root.Error(), "already added"):
        log.Warn().Msg("duplicate plugin, skipping")
    case strings.Contains(root.Error(), "cyclical dependency"):
        log.Error().Err(root).Msg("fix plugin dependency graph")
    default:
        return err
    }
}

Prevention

When it happens

Trigger: load() calls addPlugin and the dependency registry rejects the plugin: either a plugin with the same Name() already exists, or registering it would create a dependency cycle.

Common situations: Loading two plugin files that both report the same Name(); re-running the plugin load path without cleanup; mutually-dependent plugin configurations.

Related errors


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