{"record":{"id":"874701e8a06ba81c","repo":"microsoft/aspire","slug":"circular-dependency-detected-string-join-visited-parent","errorCode":null,"errorMessage":"Circular dependency detected: {string.Join(\" -> \", visited)} -> {parent}","messagePattern":"Circular dependency detected: (.+?) -> (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting/Orchestrator/RelationshipEvaluator.cs","lineNumber":77,"sourceCode":"        }\n\n        var childToParentLookup = relationships.ToDictionary(x => x.Child, x => x.Parent);\n\n        // ensure no circular dependencies\n        var visited = new Stack<IResource>();\n        foreach (var relation in relationships)\n        {\n            ValidateNoCircularDependencies(childToParentLookup, relation.Child, visited);\n        }\n\n        static void ValidateNoCircularDependencies(Dictionary<IResource, IResource> childToParentLookup, IResource child, Stack<IResource> visited)\n        {\n            visited.Push(child);\n            if (childToParentLookup.TryGetValue(child, out var parent))\n            {\n                if (visited.Contains(parent))\n                {\n                    throw new InvalidOperationException($\"Circular dependency detected: {string.Join(\" -> \", visited)} -> {parent}\");\n                }\n                ValidateNoCircularDependencies(childToParentLookup, parent, visited);\n            }\n            visited.Pop();\n        }\n    }\n}\n","sourceCodeStart":59,"sourceCodeEnd":85,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting/Orchestrator/RelationshipEvaluator.cs#L59-L85","documentation":"RelationshipEvaluator.ValidateRelationships walks the child→parent annotation graph (from WaitAnnotation/parent-child annotations) pushing each node onto a visited stack; if a node is re-encountered on the current path, the parent links form a cycle and it throws InvalidOperationException showing the chain. Cycles make a well-defined parent-child (and thus start/stop) ordering impossible, so the AppHost refuses to build the relationship model.","triggerScenarios":"Adding WaitAnnotation / WaitFor or parent-child relationships between resources such that A waits on B, B waits on C, and C waits on A (directly or transitively) — detected while GetParentChildRelationshipsFromAnnotations builds the graph at app model finalization.","commonSituations":"Mutual WaitFor calls between two services; adding waits after refactoring without noticing an existing reverse dependency; generating relationships programmatically in loops where each resource waits on the next.","solutions":["Read the cycle chain in the message and remove one WaitFor/wait dependency to break the loop.","Replace the cyclic wait with a one-directional dependency (the genuinely dependent side waits).","If mutual readiness is needed, use health-check/endpoint-based readiness instead of a bidirectional WaitFor.","Search the AppHost source for .WaitFor( calls involving the named resources and reorder."],"exampleFix":"// before (circular)\nvar a = builder.AddProject<Projects.A>(\"a\").WaitFor(b);\nvar b = builder.AddProject<Projects.B>(\"b\").WaitFor(a);\n\n// after (acyclic)\nvar a = builder.AddProject<Projects.A>(\"a\");\nvar b = builder.AddProject<Projects.B>(\"b\").WaitFor(a);","handlingStrategy":"validation","validationCode":"// Detect cycles in wait/parent annotations before building the AppHost model.\nvar waits = new Dictionary<string, string>(); // child -> parent edges, fill from WaitFor calls\nvar visiting = new HashSet<string>();\nbool HasCycle(string node, HashSet<string> stack)\n{\n    if (!stack.Add(node)) return true;\n    if (waits.TryGetValue(node, out var parent) && HasCycle(parent, stack)) return true;\n    stack.Remove(node);\n    return false;\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    builder.Build(); // or Distributor.Run()\n}\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Circular dependency detected:\"))\n{\n    logger.LogError(ex, \"Remove one WaitFor to break the cycle shown in the chain.\");\n    throw;\n}","preventionTips":["Keep WaitFor relationships strictly one-directional between any pair of resources.","Draw the dependency graph in the AppHost when using more than a few WaitFor calls.","Avoid programmatic loops that add WaitFor between successive resources.","Use health checks or resource events instead of WaitFor for mutual readiness needs."],"tags":["aspire","circular-dependency","orchestration","app-model"],"backgroundTag":"invalid-state-transition","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"}