{"record":{"id":"b73fbec0a73ccd21","repo":"invoke-ai/InvokeAI","slug":"edge-creates-a-cycle-in-the-graph-edge","errorCode":null,"errorMessage":"Edge creates a cycle in the graph ({edge})","messagePattern":"Edge creates a cycle in the graph \\((.+?)\\)","errorType":"validation","errorClass":"InvalidEdgeError","httpStatus":null,"severity":"error","filePath":"invokeai/app/services/shared/graph.py","lineNumber":1930,"sourceCode":"\n    def _get_edge_nodes(self, edge: Edge) -> tuple[BaseInvocation, BaseInvocation]:\n        try:\n            return self.get_node(edge.source.node_id), self.get_node(edge.destination.node_id)\n        except NodeNotFoundError:\n            raise InvalidEdgeError(f\"One or both nodes don't exist ({edge})\")\n\n    def _validate_edge_destination_uniqueness(self, edge: Edge, destination_node: BaseInvocation) -> None:\n        input_edges = self._get_input_edges(edge.destination.node_id, edge.destination.field)\n        if len(input_edges) > 0 and (\n            not isinstance(destination_node, CollectInvocation) or edge.destination.field != ITEM_FIELD\n        ):\n            raise InvalidEdgeError(f\"Edge already exists ({edge})\")\n\n    def _validate_edge_would_not_create_cycle(self, edge: Edge) -> None:\n        graph = self.nx_graph_flat()\n        graph.add_edge(edge.source.node_id, edge.destination.node_id)\n        if not nx.is_directed_acyclic_graph(graph):\n            raise InvalidEdgeError(f\"Edge creates a cycle in the graph ({edge})\")\n\n    def _validate_edge_field_compatibility(\n        self, edge: Edge, source_node: BaseInvocation, destination_node: BaseInvocation\n    ) -> None:\n        if isinstance(destination_node, CallSavedWorkflowInvocation) and is_call_saved_workflow_dynamic_input(\n            edge.destination.field\n        ):\n            return\n        if not are_connections_compatible(source_node, edge.source.field, destination_node, edge.destination.field):\n            raise InvalidEdgeError(f\"Field types are incompatible ({edge})\")\n\n    def _validate_iterator_edge_rules(\n        self, edge: Edge, source_node: BaseInvocation, destination_node: BaseInvocation\n    ) -> None:\n        if isinstance(destination_node, IterateInvocation) and edge.destination.field == COLLECTION_FIELD:\n            err = self._is_iterator_connection_valid(edge.destination.node_id, new_input=edge.source)\n            if err is not None:\n                raise InvalidEdgeError(f\"Iterator input type does not match iterator output type ({edge}): {err}\")","sourceCodeStart":1912,"sourceCodeEnd":1948,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/shared/graph.py#L1912-L1948","documentation":"InvalidEdgeError raised by _validate_edge_would_not_create_cycle() when adding a specific edge would introduce a cycle. It builds nx_graph_flat(), temporarily adds the proposed edge, and rejects it if the result is no longer a DAG. This is the add-time counterpart to the whole-graph CyclicalGraphError check.","triggerScenarios":"Calling g.add_edge(source, ..., dest, ...) where dest already (transitively) feeds source, so the new edge closes a loop.","commonSituations":"Rewiring feedback loops (e.g. denoise -> preview -> denoise), connecting an output of a downstream node back to an earlier node, scripted wiring computed from user input in the UI.","solutions":["Reverse or remove the proposed edge; data must flow one direction","Insert an IterateInvocation/CollectInvocation to express iteration instead of a feedback edge","Before adding, check reachability: if dest can reach source, skip the edge","Restructure the workflow so each node's inputs depend only on earlier nodes"],"exampleFix":"// before\ng.add_edge(denoise, \"latents\", load_image, \"image\")  # feedback loop\n// after\ng.add_edge(load_image, \"image\", denoise, \"image\")  # forward edge","handlingStrategy":"validation","validationCode":"import networkx as nx\n\ndef edge_creates_cycle(graph, source_id: str, dest_id: str) -> bool:\n    return nx.has_path(graph.nx_graph_flat(), dest_id, source_id)\n\nif not edge_creates_cycle(g, src, dst):\n    g.add_edge(src, \"image\", dst, \"image\")","typeGuard":"def is_safe_edge(graph, source_id: str, dest_id: str) -> bool:\n    g = graph.nx_graph_flat()\n    return not (g.has_node(source_id) and g.has_node(dest_id)\n                and nx.has_path(g, dest_id, source_id))","tryCatchPattern":"from invokeai.app.services.shared.graph import InvalidEdgeError\n\ntry:\n    g.add_edge(a, \"image\", b, \"image\")\nexcept InvalidEdgeError as e:\n    if \"creates a cycle\" in str(e):\n        logger.warning(\"skipped feedback edge a->b\")\n    else:\n        raise","preventionTips":["Before wiring, check whether the destination can already reach the source","Model iteration with IterateInvocation, not backward edges","Keep workflow data flow strictly left-to-right/forward in the UI"],"tags":["invokeai","graph-validation","cycle","edges"],"backgroundTag":"edge-creates-cycle","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}