{"record":{"id":"14fd13a5d646c2dd","repo":"semaphoreui/semaphore","slug":"cyclic-dependency-detected-involving-s","errorCode":null,"errorMessage":"cyclic dependency detected involving %s","messagePattern":"cyclic dependency detected involving (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/export/Exporter.go","lineNumber":363,"sourceCode":"\t\tn, err := strconv.Atoi(v)\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout[i] = n\n\t}\n\treturn out, nil\n}\n\nfunc getSortedKeys(exporters map[string]TypeExporter, dependsOn func(t TypeExporter) []string) ([]string, error) {\n\tvar sorted []string\n\tvisited := make(map[string]bool)\n\tvisiting := make(map[string]bool)\n\n\tvar visit func(name string) error\n\tvisit = func(name string) error {\n\t\tif visiting[name] {\n\t\t\treturn fmt.Errorf(\"cyclic dependency detected involving %s\", name)\n\t\t}\n\t\tif visited[name] {\n\t\t\treturn nil\n\t\t}\n\n\t\tvisiting[name] = true\n\n\t\tif exporter, ok := exporters[name]; ok {\n\n\t\t\torder := dependsOn(exporter)\n\n\t\t\tfor _, dep := range order {\n\t\t\t\tif _, exists := exporters[dep]; exists {\n\t\t\t\t\tif err := visit(dep); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/export/Exporter.go#L345-L381","documentation":"The export chain validates the dependency order of exporter types as a directed graph before exporting. If the dependency graph contains a cycle, visiting() detects a node that is currently on the visit stack and aborts with the name of one type involved in the cycle.","triggerScenarios":"Registering exporters whose ExportOrder/dependency declarations form a cycle (A depends on B, B on A — possibly through longer chains), then running the topological sort during export/import.","commonSituations":"Adding a new custom exporter that declares a dependency on a type which (transitively) depends back on it; refactoring dependency lists so two entity types mutually reference each other.","solutions":["Break the cycle by removing or reordering the dependency declaration between the implicated types","Restructure the custom exporter so dependencies flow one way (e.g. split into two passes)","Trace dependencies starting from the type named in the message to find the loop edge"],"exampleFix":"// before: project depends on template, template depends on project\nexporter.addDependency(\"template\") // in project exporter\nexporter.addDependency(\"project\")  // in template exporter -> cycle\n// after: remove the back-edge\n// template no longer declares a dependency on project","handlingStrategy":"try-catch","validationCode":"// detect cycle before running export\ndeps := map[string][]string{\"a\": {\"b\"}, \"b\": {\"a\"}}\nvar hasCycle func(n string, stack map[string]bool) bool\nhasCycle = func(n string, stack map[string]bool) bool {\n    if stack[n] { return true }\n    stack[n] = true\n    for _, d := range deps[n] { if hasCycle(d, stack) { return true } }\n    stack[n] = false\n    return false\n}","typeGuard":null,"tryCatchPattern":"if err := chain.export(); err != nil {\n    if strings.Contains(err.Error(), \"cyclic dependency\") {\n        log.Fatalf(\"fix exporter dependency declarations: %v\", err)\n    }\n    return err\n}","preventionTips":["Keep exporter dependencies strictly one-directional","Run a topological-sort unit test on the chain at startup","Avoid mutual references between custom exporters"],"tags":["go","export","graph","cyclic-dependency"],"backgroundTag":"cyclic-dependency-detected","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}