{"record":{"id":"3d0854aa3c17804c","repo":"spectreconsole/spectre.console","slug":"cycle-detected-in-tree-unable-to-render","errorCode":null,"errorMessage":"Cycle detected in tree - unable to render.","messagePattern":"Cycle detected in tree - unable to render\\.","errorType":"exception","errorClass":"CircularTreeException","httpStatus":null,"severity":"error","filePath":"src/Spectre.Console/Widgets/Tree.cs","lineNumber":88,"sourceCode":"        while (stack.Count > 0)\n        {\n            var stackNode = stack.Pop();\n            if (stackNode.Count == 0)\n            {\n                levels.RemoveLast();\n                if (levels.Count > 0)\n                {\n                    levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.Fork));\n                }\n\n                continue;\n            }\n\n            var isLastChild = stackNode.Count == 1;\n            var current = stackNode.Dequeue();\n            if (!visitedNodes.Add(current))\n            {\n                throw new CircularTreeException(\"Cycle detected in tree - unable to render.\");\n            }\n\n            stack.Push(stackNode);\n\n            if (isLastChild)\n            {\n                levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.End));\n            }\n\n            var prefix = levels.Skip(1).ToList();\n            var renderableLines = Segment.SplitLines(current.Renderable.Render(options, maxWidth - Segment.CellCount(prefix)));\n\n            foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate())\n            {\n                if (prefix.Count > 0)\n                {\n                    result.AddRange(prefix.ToList());\n                }","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/spectreconsole/spectre.console/blob/0acc92fada6c42f13984e79c2b5f3d993bdfb099/src/Spectre.Console/Widgets/Tree.cs#L70-L106","documentation":"When rendering a Tree, Spectre.Console walks nodes depth-first, tracking visited nodes in a HashSet<TreeNode> (reference-based). If a TreeNode is reached a second time (HashSet.Add returns false), it throws CircularTreeException — the structure is a graph with a cycle, not a tree, and cannot be laid out. This protects against infinite rendering loops.","triggerScenarios":"Adding a node back into its own subtree: parent.Nodes contains a descendant that (transitively) lists parent again, e.g. node.AddNode(ancestorNode); or reusing the same TreeNode reference such that it is reachable through two paths forming a loop.","commonSituations":"Building a Tree from graph/dependency data that contains cycles (package dependency cycles, org-chart loops); accidentally adding a parent as a child of one of its descendants; sharing a TreeNode instance across branches in a way that creates a reference loop.","solutions":["Run a cycle check (DFS with a visited set of TreeNode references) over the root before rendering.","Ensure each TreeNode instance is added as a child of at most one parent (trees, not graphs).","If the data is a graph, break cycles by choosing a spanning tree and omitting back-edges.","Catch CircularTreeException at the render call to fail gracefully with a diagnostic."],"exampleFix":"// before\nroot.AddNode(child);\nchild.AddNode(root); // root now in its own subtree -> cycle\nAnsiConsole.Write(tree);\n\n// after\nroot.AddNode(child);\n// do not add root (or any ancestor) under its descendants\nAnsiConsole.Write(tree);","handlingStrategy":"try-catch","validationCode":"bool HasCycle(TreeNode root)\n{\n    var seen = new HashSet<TreeNode>();\n    bool Dfs(TreeNode n)\n    {\n        if (!seen.Add(n)) return false;\n        foreach (var child in n.Nodes)\n            if (!Dfs(child)) return true; // revisit -> cycle\n        seen.Remove(n);\n        return false;\n    }\n    return Dfs(root);\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    AnsiConsole.Write(tree);\n}\ncatch (CircularTreeException ex)\n{\n    AnsiConsole.WriteLine($\"Refusing to render cyclic tree: {ex.Message}\");\n}","preventionTips":["Run a DFS cycle check over TreeNode references before rendering.","Treat TreeNode as single-parent: each instance belongs to at most one parent.","When modeling graphs, extract a spanning tree and omit back-edges before rendering.","Never add an ancestor node into its own descendant subtree."],"tags":["tree","cycle","render","graph","circular-reference"],"backgroundTag":null,"analyzedSha":"0acc92fada6c42f13984e79c2b5f3d993bdfb099","analyzedAt":"2026-08-13T18:27:21.983Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}