{"record":{"id":"2cc90ca1209ab8f1","repo":"microsoft/aspire","slug":"circular-dependency-detected-in-pipeline-steps-string-join","errorCode":null,"errorMessage":"Circular dependency detected in pipeline steps: {string.Join(\" → \", cycle)}","messagePattern":"Circular dependency detected in pipeline steps: (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs","lineNumber":1097,"sourceCode":"\n        var visitStates = new Dictionary<string, VisitState>(steps.Count, StringComparer.Ordinal);\n        foreach (var step in steps)\n        {\n            visitStates[step.Name] = VisitState.Unvisited;\n        }\n\n        // DFS to detect cycles\n        void DetectCycles(string stepName, Stack<string> path)\n        {\n            if (!visitStates.TryGetValue(stepName, out var state))\n            {\n                return;\n            }\n\n            if (state == VisitState.Visiting) // Currently visiting - cycle detected!\n            {\n                var cycle = path.Reverse().SkipWhile(s => s != stepName).Append(stepName);\n                throw new InvalidOperationException(\n                    $\"Circular dependency detected in pipeline steps: {string.Join(\" → \", cycle)}\");\n            }\n\n            if (state == VisitState.Visited) // Already fully visited - no need to check again\n            {\n                return;\n            }\n\n            visitStates[stepName] = VisitState.Visiting;\n            path.Push(stepName);\n\n            if (stepsByName.TryGetValue(stepName, out var step))\n            {\n                foreach (var dependency in step.DependsOnSteps)\n                {\n                    DetectCycles(dependency, path);\n                }\n            }","sourceCodeStart":1079,"sourceCodeEnd":1115,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs#L1079-L1115","documentation":"Before executing, the pipeline topologically sorts steps (DFS with VisitState tracking). If the DFS re-enters a step that is currently on the visiting stack, the steps form a cycle; the pipeline throws an InvalidOperationException listing the cycle path joined with ' → ', naming each step in the loop.","triggerScenarios":"Registering steps whose DependsOnSteps/RequiredBySteps edges form a loop, e.g. A depends on B, B depends on A; or A depends on B, B depends on C, C depends on A. Also occurs when RequiredBySteps edges, combined with dependency edges, accidentally create mutual ordering between two steps.","commonSituations":"Wiring steps symmetrically by mistake (A.DependsOn(B) and B.DependsOn(A)); copy-pasting dependency wiring that points a later step back to an earlier one; a refactor that moved a dependency from one step to another creating an unintended loop; string-based wiring across packages where two integrations each declare they depend on the other's step.","solutions":["Read the cycle path in the message and break the loop by removing or correcting one DependsOn/RequiredBy edge","Decide the true ordering: keep only the edge in the direction that should execute first","If the ordering genuinely is mutual, merge the two steps into one or split the shared work into a third step both depend on","Draw the graph from all DependsOnSteps/RequiredBySteps registrations before wiring new steps to spot cycles early"],"exampleFix":"// before\nstepA.DependsOn(\"B\");\nstepB.DependsOn(\"A\"); // cycle\n// after\nstepA.DependsOn(\"B\"); // B runs first, then A\n","handlingStrategy":"validation","validationCode":"// Before wiring, assert the dependency graph is acyclic\nvar deps = steps.ToDictionary(s => s.Name, s => s.DependsOnSteps);\nvar visiting = new HashSet<string>(); var done = new HashSet<string>();\nvoid Visit(string n)\n{\n    if (done.Contains(n)) return;\n    if (!visiting.Add(n)) throw new InvalidOperationException($\"Cycle at {n}\");\n    foreach (var d in deps.GetValueOrDefault(n, Enumerable.Empty<string>())) Visit(d);\n    visiting.Remove(n); done.Add(n);\n}\nforeach (var n in deps.Keys) Visit(n);","typeGuard":null,"tryCatchPattern":"try { await pipeline.ExecuteAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Circular dependency detected\"))\n{\n    // ex.Message contains the full cycle path 'A → B → A'; remove one edge\n}","preventionTips":["Enforce one-directional ordering: use DependsOn OR RequiredBy for a pair, never both","Keep a single source of truth for step ordering rather than scattering edges across packages","Sketch the dependency graph (topological order) whenever you add a new edge","When splitting a step, re-check its old dependents and dependencies for newly created loops"],"tags":["pipeline","circular-dependency","validation","dependency-graph"],"backgroundTag":"circular-dependency","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}