{"record":{"id":"faaa39bf1fefe9e4","repo":"microsoft/autogen","slug":"cycle-detected-without-exit-condition-joi","errorCode":null,"errorMessage":"Cycle detected without exit condition: {' -> '.join(cycle_nodes + cycle_nodes[:1])}","messagePattern":"Cycle detected without exit condition: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_graph/_digraph_group_chat.py","lineNumber":183,"sourceCode":"            visited.add(node_name)\n            rec_stack.add(node_name)\n            path.append(node_name)\n            cycle = False\n\n            for edge in self.nodes[node_name].edges:\n                target = edge.target\n                if target not in visited:\n                    if dfs(target):\n                        cycle = True\n                elif target in rec_stack:\n                    # Found a cycle → extract the cycle\n                    cycle_start_index = path.index(target)\n                    cycle_nodes = path[cycle_start_index:]\n                    cycle_edges: List[DiGraphEdge] = []\n                    for n in cycle_nodes:\n                        cycle_edges.extend(self.nodes[n].edges)\n                    if all(edge.condition is None and edge.condition_function is None for edge in cycle_edges):\n                        raise ValueError(\n                            f\"Cycle detected without exit condition: {' -> '.join(cycle_nodes + cycle_nodes[:1])}\"\n                        )\n                    cycle = True  # Found cycle, but it has an exit condition\n\n            rec_stack.remove(node_name)\n            path.pop()\n            return cycle\n\n        has_cycle = False\n        for node in self.nodes:\n            if node not in visited:\n                if dfs(node):\n                    has_cycle = True\n\n        return has_cycle\n\n    def get_has_cycles(self) -> bool:\n        \"\"\"Indicates if the graph has at least one cycle (with valid exit conditions).\"\"\"","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_graph/_digraph_group_chat.py#L165-L201","documentation":"Raised by DiGraph.has_cycles_with_exit during graph validation when a directed cycle is found in which every edge is unconditional (neither a textual condition nor a condition_function). An unconditional cycle has no way to exit, so the chat would loop forever; validation therefore rejects it. Cycles are allowed only when at least one edge in the cycle provides an exit condition.","triggerScenarios":"Using DiGraphBuilder.add_edge in a loop (e.g. reviewer -> reviser -> reviewer) without condition= or condition_function= on any edge of the cycle; converting a linear workflow to a cyclic one while forgetting to add the exit condition; typos making a condition evaluate falsy at build time is not the issue here — the check is purely structural (condition is None).","commonSituations":"Draft/review/revise loops in GraphFlow; porting workflows from other frameworks where loops are bounded externally; adding a 'back edge' for retries without specifying when to stop.","solutions":["Add condition=\"...\" or condition_function=... to at least one edge in the cycle (typically the back-edge), describing when to leave the loop.","Alternatively break the cycle with a conditional edge to an exit/leaf node.","If the loop is intentionally bounded, also consider max_turns on the team as a safety net (though the structural fix is still required)."],"exampleFix":"// before\nbuilder.add_edge(reviewer, reviser)\nbuilder.add_edge(reviser, reviewer)  # unconditional cycle -> error\n\n// after\nbuilder.add_edge(reviewer, reviser, condition=\"revision needed\")\nbuilder.add_edge(reviser, reviewer, condition_function=lambda m: \"approved\" not in str(m).lower())\nbuilder.add_edge(reviewer, finalizer, condition=\"approved\")","handlingStrategy":"validation","validationCode":"def cycle_edges_have_exit(builder: DiGraphBuilder) -> bool:\n    try:\n        builder.build().graph_validate()\n        return True\n    except ValueError as e:\n        if \"Cycle detected without exit condition\" in str(e):\n            return False\n        raise","typeGuard":"def edge_is_conditional(edge) -> bool:\n    return edge.condition is not None or edge.condition_function is not None","tryCatchPattern":"try:\n    flow = GraphFlow(participants, graph_builder=builder)\nexcept ValueError as e:\n    if \"Cycle detected without exit condition\" in str(e):\n        # add condition/condition_function to at least one edge in the reported cycle\n        ...","preventionTips":["Always attach a condition or condition_function to back-edges in loops.","Unit-test graph validation for every workflow graph you author.","Model loops as: entry -> [work] -> conditional back-edge, plus conditional exit edge to a leaf."],"tags":["graph","cycles","validation","workflow"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}