{"record":{"id":"0567881eb1314c41","repo":"m1k1o/neko","slug":"plugin-s-already-added","errorCode":null,"errorMessage":"plugin '%s' already added","messagePattern":"plugin '(.+?)' already added","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/plugins/dependency.go","lineNumber":71,"sourceCode":"\t}\n\n\ta.logger.Info().Str(\"plugin\", a.plugin.Name()).Msg(\"plugin started\")\n\treturn nil\n}\n\ntype dependiencies struct {\n\tdeps   map[string]*dependency\n\tlogger zerolog.Logger\n}\n\nfunc (d *dependiencies) addPlugin(plugin types.Plugin) error {\n\tpluginName := plugin.Name()\n\n\tplug, ok := d.deps[pluginName]\n\tif !ok {\n\t\tplug = &dependency{}\n\t} else if plug.plugin != nil {\n\t\treturn fmt.Errorf(\"plugin '%s' already added\", pluginName)\n\t}\n\n\tplug.plugin = plugin\n\tplug.logger = d.logger\n\td.deps[pluginName] = plug\n\n\tdplug, ok := plugin.(types.DependablePlugin)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tfor _, depName := range dplug.DependsOn() {\n\t\tdependsOn, ok := d.deps[depName]\n\t\tif !ok {\n\t\t\tdependsOn = &dependency{}\n\t\t} else if dependsOn.plugin != nil {\n\t\t\t// if there is a cyclical dependency, break it and return error\n\t\t\tif tdep, ok := dependsOn.findPlugin(pluginName); ok {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/plugins/dependency.go#L53-L89","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Give each plugin a unique value from Name() (e.g. include a namespace or version)","Check the load/init flow for duplicate registration of the same plugin and load it only once","If reloading, remove the old dependency entry (delete from d.deps) before calling addPlugin again","Wrap addPlugin in an errors.Is check for this message and log which plugin name collided"],"exampleFix":"// before\nfunc (p *myPlugin) Name() string { return \"filetransfer\" }\n// two copies loaded -> \"plugin 'filetransfer' already added\"\n\n// after\nfunc (p *myPlugin) Name() string {\n    if p.variant == \"beta\" { return \"filetransfer-beta\" }\n    return \"filetransfer\"\n}","handlingStrategy":"validation","validationCode":"if _, ok := manager.plugins.findPlugin(p.Name()); ok {\n    return fmt.Errorf(\"plugin %q already registered\", p.Name())\n}\nreturn manager.plugins.addPlugin(p)","typeGuard":null,"tryCatchPattern":"if err := manager.plugins.addPlugin(p); err != nil {\n    if strings.Contains(err.Error(), \"already added\") {\n        log.Warn().Str(\"plugin\", p.Name()).Msg(\"plugin already registered, skipping\")\n        return nil\n    }\n    return err\n}","preventionTips":["Enforce unique plugin.Name() values via a unit test over all registered plugins","Load each plugin artifact exactly once at startup","For hot-reload, explicitly remove the old dependency entry before re-adding"],"tags":["go","plugins","duplicate-registration"],"backgroundTag":"duplicate-plugin-registration","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}