n8n-io/n8n · error · UnimplementedError
unimplemented
unimplemented
Error message
Graphs with back-edges (loops) are not supported yet
What it means
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.
Source
Thrown at packages/@n8n/engine/src/graph/validate-executable-graph.ts:34
* is created for it. The single place executability rules live; new rules are
* added here as they arise.
*
* Throws `GraphValidationError` for graphs that can never run, and
* `UnimplementedError` for shapes the engine doesn't support yet.
*/
export function validateExecutableGraph(graph: WorkflowGraph): void {
const triggers = graph.nodes.filter((node) => node.type === 'trigger');
if (triggers.length === 0) {
throw new GraphValidationError('Graph has no trigger node to start from');
}
if (triggers.length > 1) {
throw new GraphValidationError('Graph must have exactly one trigger node');
}
// TODO(CAT-2875): loop iteration needs re-runnable steps; until that lands,
// graphs with back-edges are rejected outright rather than deadlocking.
if (graph.edges.some((edge) => edge.isBackEdge)) {
throw new UnimplementedError('Graphs with back-edges (loops) are not supported yet');
}
// Slot indices are structural, so they're enforced here rather than left to
// the transport boundary. TODO(CAT-3042): enforce an upper bound too.
for (const edge of graph.edges) {
for (const index of [edge.outputIndex, edge.inputIndex]) {
if (!Number.isInteger(index) || index < 0) {
throw new GraphValidationError(
`Edge ${edge.from} → ${edge.to} has slot index ${index}; slot indices are non-negative integers`,
);
}
if (index > MAX_SLOT_INDEX) {
throw new GraphValidationError(
`Edge ${edge.from} → ${edge.to} has slot index ${index}; slot indices above ${MAX_SLOT_INDEX} are not supported yet`,
);
}
}
}View on GitHub (pinned to 5ac6606e81)
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.
Example fix
// before
edges: [
{ from: 'A', to: 'B', outputIndex: 0, inputIndex: 0, isBackEdge: false },
{ from: 'B', to: 'A', outputIndex: 0, inputIndex: 0, isBackEdge: true }, // loop
]
// after
// move iteration inside node B; graph stays acyclic
edges: [
{ from: 'A', to: 'B', outputIndex: 0, inputIndex: 0, isBackEdge: false },
] Defensive patterns
Strategy: validation
Validate before calling
function isAcyclic(graph: WorkflowGraph): boolean {
return !graph.edges.some(e => e.isBackEdge);
}
if (!isAcyclic(graph)) { // do not submit; refactor the loop into a node } Type guard
function isAcyclicGraph(graph: WorkflowGraph): boolean {
return graph.edges.every(e => !e.isBackEdge);
} Try / catch
try {
validateExecutableGraph(graph);
} catch (err) {
if (err instanceof UnimplementedError && /back-edges/.test(err.message)) {
// unroll iterations or move loop into a node
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Edge ${edge.from} → ${edge.to} has slot index ${index}; slot
- invalid_graph
- Edge ${edge.from} → ${edge.to} leaves output slot ${edge.out
- Node ${edge.to} has more than one edge into input slot ${edg
- unimplemented
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/92151c9072def83d.
Report an issue: GitHub.