{"record":{"id":"f5e8358776fda15f","repo":"microsoft/autogen","slug":"graph-must-have-at-least-one-leaf-node","errorCode":null,"errorMessage":"Graph must have at least one leaf node","messagePattern":"Graph must have at least one leaf node","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":216,"sourceCode":"        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).\"\"\"\n        if self._has_cycles is None:\n            self._has_cycles = self.has_cycles_with_exit()\n\n        return self._has_cycles\n\n    def graph_validate(self) -> None:\n        \"\"\"Validate graph structure and execution rules.\"\"\"\n        if not self.nodes:\n            raise ValueError(\"Graph has no nodes.\")\n\n        if not self.get_start_nodes():\n            raise ValueError(\"Graph must have at least one start node\")\n\n        if not self.get_leaf_nodes():\n            raise ValueError(\"Graph must have at least one leaf node\")\n\n        # Outgoing edge condition validation (per node)\n        for node in self.nodes.values():\n            # Check that if a node has an outgoing conditional edge, then all outgoing edges are conditional\n            has_condition = any(\n                edge.condition is not None or edge.condition_function is not None for edge in node.edges\n            )\n            has_unconditioned = any(edge.condition is None and edge.condition_function is None for edge in node.edges)\n            if has_condition and has_unconditioned:\n                raise ValueError(f\"Node '{node.name}' has a mix of conditional and unconditional edges.\")\n\n        # Validate activation conditions across all edges in the graph\n        self._validate_activation_conditions()\n\n        self._has_cycles = self.has_cycles_with_exit()\n\n    def _validate_activation_conditions(self) -> None:\n        \"\"\"Validate that all edges pointing to the same target node have consistent activation_condition values.","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_graph/_digraph_group_chat.py#L198-L234","documentation":"Raised by DiGraph.graph_validate when the graph has no leaf node — a node with no outgoing edges. Leaf nodes are where the workflow terminates; a graph in which every node has at least one outgoing edge (pure cycles, or a 'final' node that was accidentally given an edge) never formally ends and is rejected.","triggerScenarios":"Every node participates in an edge (e.g. A->B->C->A); a 'finalizer' node accidentally given an outgoing edge via add_edge(finalizer, other); dynamically generated graphs where the terminal step was dropped.","commonSituations":"Loops without an exit branch; refactoring that adds a 'logging' edge from the last node; config-driven graphs missing the terminal node definition.","solutions":["Add a terminal node with no outgoing edges that the workflow reaches (e.g. via a conditional edge 'approved' -> finalizer).","Remove unintended outgoing edges from the node meant to be the last step.","Verify get_leaf_nodes() non-empty in tests before constructing GraphFlow."],"exampleFix":"// before\nbuilder.add_edge(reviewer, reviser, condition=\"needs work\")\nbuilder.add_edge(reviser, reviewer)  # no leaf\n\n// after\nbuilder.add_edge(reviewer, reviser, condition=\"needs work\")\nbuilder.add_edge(reviser, reviewer, condition_function=lambda m: not _done(m))\nbuilder.add_edge(reviewer, finalizer, condition=\"done\")  # finalizer is a leaf","handlingStrategy":"validation","validationCode":"graph = builder.build()\nif not graph.get_leaf_nodes():\n    raise ValueError(\"No leaf node: add a terminal node with no outgoing edges\")\nflow = GraphFlow(participants, graph_builder=builder)","typeGuard":"def has_leaf_node(out_degree: dict[str, int]) -> bool:\n    return any(count == 0 for count in out_degree.values())","tryCatchPattern":"try:\n    flow = GraphFlow(participants, graph_builder=builder)\nexcept ValueError as e:\n    if \"at least one leaf node\" in str(e):\n        # add a terminal node reachable via a conditional exit edge\n        ...","preventionTips":["End every workflow with an explicit finalizer/reporter node that has no outgoing edges.","Double-check that 'final' nodes never gain outgoing edges during refactors.","Test graph structure (start + leaf) separately from runtime behavior."],"tags":["graph","validation","leaf-node","workflow"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}