invoke-ai/InvokeAI · error · InvalidEdgeError

Edge creates a cycle in the graph ({edge})

Error message

Edge creates a cycle in the graph ({edge})

What it means

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.

Source

Thrown at invokeai/app/services/shared/graph.py:1930

    def _get_edge_nodes(self, edge: Edge) -> tuple[BaseInvocation, BaseInvocation]:
        try:
            return self.get_node(edge.source.node_id), self.get_node(edge.destination.node_id)
        except NodeNotFoundError:
            raise InvalidEdgeError(f"One or both nodes don't exist ({edge})")

    def _validate_edge_destination_uniqueness(self, edge: Edge, destination_node: BaseInvocation) -> None:
        input_edges = self._get_input_edges(edge.destination.node_id, edge.destination.field)
        if len(input_edges) > 0 and (
            not isinstance(destination_node, CollectInvocation) or edge.destination.field != ITEM_FIELD
        ):
            raise InvalidEdgeError(f"Edge already exists ({edge})")

    def _validate_edge_would_not_create_cycle(self, edge: Edge) -> None:
        graph = self.nx_graph_flat()
        graph.add_edge(edge.source.node_id, edge.destination.node_id)
        if not nx.is_directed_acyclic_graph(graph):
            raise InvalidEdgeError(f"Edge creates a cycle in the graph ({edge})")

    def _validate_edge_field_compatibility(
        self, edge: Edge, source_node: BaseInvocation, destination_node: BaseInvocation
    ) -> None:
        if isinstance(destination_node, CallSavedWorkflowInvocation) and is_call_saved_workflow_dynamic_input(
            edge.destination.field
        ):
            return
        if not are_connections_compatible(source_node, edge.source.field, destination_node, edge.destination.field):
            raise InvalidEdgeError(f"Field types are incompatible ({edge})")

    def _validate_iterator_edge_rules(
        self, edge: Edge, source_node: BaseInvocation, destination_node: BaseInvocation
    ) -> None:
        if isinstance(destination_node, IterateInvocation) and edge.destination.field == COLLECTION_FIELD:
            err = self._is_iterator_connection_valid(edge.destination.node_id, new_input=edge.source)
            if err is not None:
                raise InvalidEdgeError(f"Iterator input type does not match iterator output type ({edge}): {err}")

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Reverse or remove the proposed edge; data must flow one direction
  2. Insert an IterateInvocation/CollectInvocation to express iteration instead of a feedback edge
  3. Before adding, check reachability: if dest can reach source, skip the edge
  4. Restructure the workflow so each node's inputs depend only on earlier nodes

Example fix

// before
g.add_edge(denoise, "latents", load_image, "image")  # feedback loop
// after
g.add_edge(load_image, "image", denoise, "image")  # forward edge
Defensive patterns

Strategy: validation

Validate before calling

import networkx as nx

def edge_creates_cycle(graph, source_id: str, dest_id: str) -> bool:
    return nx.has_path(graph.nx_graph_flat(), dest_id, source_id)

if not edge_creates_cycle(g, src, dst):
    g.add_edge(src, "image", dst, "image")

Type guard

def is_safe_edge(graph, source_id: str, dest_id: str) -> bool:
    g = graph.nx_graph_flat()
    return not (g.has_node(source_id) and g.has_node(dest_id)
                and nx.has_path(g, dest_id, source_id))

Try / catch

from invokeai.app.services.shared.graph import InvalidEdgeError

try:
    g.add_edge(a, "image", b, "image")
except InvalidEdgeError as e:
    if "creates a cycle" in str(e):
        logger.warning("skipped feedback edge a->b")
    else:
        raise

Prevention

When it happens

Trigger: Calling g.add_edge(source, ..., dest, ...) where dest already (transitively) feeds source, so the new edge closes a loop.

Common situations: 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.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/b73fbec0a73ccd21. Report an issue: GitHub.