microsoft/aspire · error · InvalidOperationException
Circular dependency detected in pipeline steps
Error message
Circular dependency detected in pipeline steps: {string.Join(" → ", cycle)} What it means
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.
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
Example fix
// before
stepA.DependsOn("B");
stepB.DependsOn("A"); // cycle
// after
stepA.DependsOn("B"); // B runs first, then A
Defensive patterns
Strategy: validation
Validate before calling
// Before wiring, assert the dependency graph is acyclic
var deps = steps.ToDictionary(s => s.Name, s => s.DependsOnSteps);
var visiting = new HashSet<string>(); var done = new HashSet<string>();
void Visit(string n)
{
if (done.Contains(n)) return;
if (!visiting.Add(n)) throw new InvalidOperationException($"Cycle at {n}");
foreach (var d in deps.GetValueOrDefault(n, Enumerable.Empty<string>())) Visit(d);
visiting.Remove(n); done.Add(n);
}
foreach (var n in deps.Keys) Visit(n); Try / catch
try { await pipeline.ExecuteAsync(); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Circular dependency detected"))
{
// ex.Message contains the full cycle path 'A → B → A'; remove one edge
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Duplicate step name
- getValue is only available on server-returned…
- Step ' ' depends on unknown step
- Step ' ' is required by unknown step
- Step ' ' is required by unknown step
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/2cc90ca1209ab8f1.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs:1097
var visitStates = new Dictionary<string, VisitState>(steps.Count, StringComparer.Ordinal);
foreach (var step in steps)
{
visitStates[step.Name] = VisitState.Unvisited;
}
// DFS to detect cycles
void DetectCycles(string stepName, Stack<string> path)
{
if (!visitStates.TryGetValue(stepName, out var state))
{
return;
}
if (state == VisitState.Visiting) // Currently visiting - cycle detected!
{
var cycle = path.Reverse().SkipWhile(s => s != stepName).Append(stepName);
throw new InvalidOperationException(
$"Circular dependency detected in pipeline steps: {string.Join(" → ", cycle)}");
}
if (state == VisitState.Visited) // Already fully visited - no need to check again
{
return;
}
visitStates[stepName] = VisitState.Visiting;
path.Push(stepName);
if (stepsByName.TryGetValue(stepName, out var step))
{
foreach (var dependency in step.DependsOnSteps)
{
DetectCycles(dependency, path);
}
}View on GitHub (pinned to 25830f84bd)