{"record":{"id":"92151c9072def83d","repo":"n8n-io/n8n","slug":"unimplemented","errorCode":"unimplemented","errorMessage":"Graphs with back-edges (loops) are not supported yet","messagePattern":"Graphs with back-edges \\(loops\\) are not supported yet","errorType":"http","errorClass":"UnimplementedError","httpStatus":501,"severity":"error","filePath":"packages/@n8n/engine/src/graph/validate-executable-graph.ts","lineNumber":34,"sourceCode":" * is created for it. The single place executability rules live; new rules are\n * added here as they arise.\n *\n * Throws `GraphValidationError` for graphs that can never run, and\n * `UnimplementedError` for shapes the engine doesn't support yet.\n */\nexport function validateExecutableGraph(graph: WorkflowGraph): void {\n\tconst triggers = graph.nodes.filter((node) => node.type === 'trigger');\n\tif (triggers.length === 0) {\n\t\tthrow new GraphValidationError('Graph has no trigger node to start from');\n\t}\n\tif (triggers.length > 1) {\n\t\tthrow new GraphValidationError('Graph must have exactly one trigger node');\n\t}\n\n\t// TODO(CAT-2875): loop iteration needs re-runnable steps; until that lands,\n\t// graphs with back-edges are rejected outright rather than deadlocking.\n\tif (graph.edges.some((edge) => edge.isBackEdge)) {\n\t\tthrow new UnimplementedError('Graphs with back-edges (loops) are not supported yet');\n\t}\n\n\t// Slot indices are structural, so they're enforced here rather than left to\n\t// the transport boundary. TODO(CAT-3042): enforce an upper bound too.\n\tfor (const edge of graph.edges) {\n\t\tfor (const index of [edge.outputIndex, edge.inputIndex]) {\n\t\t\tif (!Number.isInteger(index) || index < 0) {\n\t\t\t\tthrow new GraphValidationError(\n\t\t\t\t\t`Edge ${edge.from} → ${edge.to} has slot index ${index}; slot indices are non-negative integers`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (index > MAX_SLOT_INDEX) {\n\t\t\t\tthrow new GraphValidationError(\n\t\t\t\t\t`Edge ${edge.from} → ${edge.to} has slot index ${index}; slot indices above ${MAX_SLOT_INDEX} are not supported yet`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/engine/src/graph/validate-executable-graph.ts#L16-L52","documentation":"UnimplementedError (code: unimplemented) thrown by validateExecutableGraph when any edge has isBackEdge === true. The engine does not yet support loops; until CAT-2875 lands (re-runnable steps for loop iteration), back-edges are rejected outright to prevent deadlock rather than failing at runtime.","triggerScenarios":"Submitting a graph where at least one edge creates a cycle (target precedes source in topological order, marked isBackEdge). Any loop/feedback construct trips this.","commonSituations":"A user models a retry/feedback loop or iterative process; graph synthesis code generates cyclic dependencies; converting a DAG-based workflow that used a different loop primitive; an importer that preserves cycles from another format.","solutions":["Refactor the loop out of the graph: unroll a bounded number of iterations, or move the loop into a single node's runtime logic.","Track CAT-2875 for native loop support and avoid cyclic edges until then.","If the back-edge is accidental (miswired connection), remove or redirect it.","For retry patterns, use a node that handles its own retry internally instead of a graph-level cycle."],"exampleFix":"// before\nedges: [\n  { from: 'A', to: 'B', outputIndex: 0, inputIndex: 0, isBackEdge: false },\n  { from: 'B', to: 'A', outputIndex: 0, inputIndex: 0, isBackEdge: true }, // loop\n]\n\n// after\n// move iteration inside node B; graph stays acyclic\nedges: [\n  { from: 'A', to: 'B', outputIndex: 0, inputIndex: 0, isBackEdge: false },\n]","handlingStrategy":"validation","validationCode":"function isAcyclic(graph: WorkflowGraph): boolean {\n  return !graph.edges.some(e => e.isBackEdge);\n}\nif (!isAcyclic(graph)) { // do not submit; refactor the loop into a node }","typeGuard":"function isAcyclicGraph(graph: WorkflowGraph): boolean {\n  return graph.edges.every(e => !e.isBackEdge);\n}","tryCatchPattern":"try {\n  validateExecutableGraph(graph);\n} catch (err) {\n  if (err instanceof UnimplementedError && /back-edges/.test(err.message)) {\n    // unroll iterations or move loop into a node\n  } else throw err;\n}","preventionTips":["Avoid cycles until CAT-2875 ships native loop support.","Model retries inside a single node rather than as a graph cycle.","Inspect importers that preserve cycles from external formats."],"tags":["graph","unimplemented","loops","engine"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}