{"record":{"id":"bf90b6509300dfac","repo":"m1k1o/neko","slug":"cyclical-dependency-detected-s-s","errorCode":null,"errorMessage":"cyclical dependency detected: '%s' <-> '%s'","messagePattern":"cyclical dependency detected: '(.+?)' <-> '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/plugins/dependency.go","lineNumber":92,"sourceCode":"\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 {\n\t\t\t\tdependsOn.dependsOn = nil\n\t\t\t\tdelete(d.deps, pluginName)\n\t\t\t\treturn fmt.Errorf(\"cyclical dependency detected: '%s' <-> '%s'\", pluginName, tdep.plugin.Name())\n\t\t\t}\n\t\t}\n\n\t\tplug.dependsOn = append(plug.dependsOn, dependsOn)\n\t\td.deps[depName] = dependsOn\n\t}\n\n\treturn nil\n}\n\nfunc (d *dependiencies) findPlugin(name string) (*dependency, bool) {\n\tfor _, dep := range d.deps {\n\t\tplug, ok := dep.findPlugin(name)\n\t\tif ok {\n\t\t\treturn plug, true\n\t\t}\n\t}\n\treturn nil, false","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/plugins/dependency.go#L74-L110","documentation":"Returned by addPlugin when wiring a plugin's dependencies would create a dependency cycle. The code detects the cycle (dependsOn.findPlugin finds the starting plugin reachable), breaks the link (sets dependsOn.dependsOn = nil and deletes the entry) to keep the graph acyclic, and returns this error. The error names the two plugins forming the cycle.","triggerScenarios":"Calling addPlugin(p) where p declares (via its dependency list) a dependency on a plugin that transitively already depends on p — e.g. plugin A depends on B, then adding B which depends on A.","commonSituations":"Refactoring plugins so two features start requiring each other; copy-pasting a DependsOn list that includes the registering plugin itself or its ancestor; config where two plugins mutually list each other as dependencies.","solutions":["Break the cycle in plugin configuration: remove the dependency edge that makes A depend on B while B depends on A","Extract shared logic into a third plugin both can depend on, restoring a DAG","Fix the plugin's dependency declaration (the DependsOn/requirements list) so a plugin never lists something that already depends on it","After this error, note the graph was mutated (cycle broken, entry deleted) — re-initialize the plugin manager before retrying with corrected deps"],"exampleFix":"// before\npluginA.DependsOn = [\"B\"]\npluginB.DependsOn = [\"A\"]  // cycle: A <-> B\n\n// after\npluginA.DependsOn = [\"B\"]\npluginB.DependsOn = []      // cycle removed\n// or: shared plugin C; A -> C, B -> C","handlingStrategy":"validation","validationCode":"// before adding, ensure no registered plugin already (transitively) depends on p\nfunc createsCycle(reg map[string]*dependency, p types.Plugin) bool {\n    var visit func(name string) bool\n    visit = func(name string) bool {\n        d, ok := reg[name]\n        if !ok || d.plugin == nil { return false }\n        if d.plugin.Name() == p.Name() { return true }\n        for _, dep := range d.dependsOn {\n            if visit(dep.Name()) { return true }\n        }\n        return false\n    }\n    for _, name := range p.Dependencies() {\n        if visit(name) { return true }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"if err := manager.plugins.addPlugin(p); err != nil {\n    if strings.Contains(err.Error(), \"cyclical dependency\") {\n        log.Error().Str(\"plugin\", p.Name()).Err(err).Msg(\"fix plugin dependency graph\")\n    }\n    return err\n}","preventionTips":["Keep plugin dependencies a strict DAG; never let a plugin depend on something that depends on it","Extract shared functionality into a lower-level plugin both depend on","Test the full dependency graph for cycles in CI before deploying"],"tags":["go","plugins","dependency-cycle"],"backgroundTag":"circular-dependency-detected","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}