n8n-io/n8n · error · Error

Maximum branch depth

Error message

Maximum branch depth (${WorkflowBuilderImpl.MAX_BRANCH_DEPTH}) exceeded while building workflow graph

What it means

A recursion guard inside addBranchToGraph. Each nested branch increments a depth counter; if it reaches MAX_BRANCH_DEPTH (500) the builder throws rather than overflow the stack. The guard exists to detect cycles in branch chains (e.g. a NodeChain that references itself) which would otherwise recurse infinitely while flattening the graph. Under normal use with finite, acyclic chains the counter never approaches 500.

Solutions

  1. Inspect the branch being added for self-reference or mutual reference between composites; break the cycle.
  2. Audit the code that constructs NodeChains/branches for a missing base case in its loop or recursion.
  3. If the depth is legitimately large, refactor into a flatter structure.

Example fix

// before (buggy loop builds a self-referential chain)
let chain = node({ type: 'Set' });
chain = chain.to(chain); // cycle
workflow.add(trigger({})).to(chain); // throws at depth 500

// after
workflow.add(trigger({})).to(node({ type: 'Set' }));
Defensive patterns

Strategy: validation

Validate before calling

const MAX = 500;
function chainDepth(chain: any, seen = new WeakSet()): number {
  // walk the chain counting nesting, guard against cycles with `seen`
  return depth;
}
if (chainDepth(branch) >= MAX) {
  throw new Error('Branch chain too deep or cyclic');
}

Type guard

function isCyclicReference(branch: any, seen = new WeakSet()): boolean {
  if (typeof branch !== 'object' || branch === null) return false;
  if (seen.has(branch)) return true;
  seen.add(branch);
  return Object.values(branch).some(v => isCyclicReference(v, seen));
}

Try / catch

try {
  wf.add(trigger({})).to(branch);
} catch (e) {
  if (e instanceof Error && /Maximum branch depth/.test(e.message)) {
    // inspect branch for self-reference and break the cycle
  }
  throw e;
}

Prevention

When it happens

Trigger: A NodeChain or composite node whose branch references itself directly or indirectly, creating a cycle; programmatically generated node chains with a loop bug; a plugin that recursively dispatches to itself.

Common situations: Code that builds NodeChains in a loop with a faulty termination condition; composites that accidentally include themselves as members; very deep (but legitimate) nested chains that genuinely exceed 500 levels (rare).

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/f40046ee7df3db98. Report an issue: GitHub.

Appendix: source

Thrown at packages/@n8n/workflow-sdk/src/workflow-builder.ts:1093

		this._currentNode = chain.tail?.name ?? headNodeName;
		this._currentOutput = 0;

		return this;
	}

	/**
	 * Add a branch to the graph, handling both single nodes and NodeChains.
	 * Returns the name of the first node in the branch (for connection from IF).
	 * @param nameMapping - Optional map from node ID to actual map key (used when nodes are renamed)
	 */
	private addBranchToGraph(
		nodes: Map<string, GraphNode>,
		branch: NodeInstance<string, string, unknown>,
		nameMapping?: Map<string, string>,
	): string {
		// Guard against infinite recursion from cycles in branch chains
		if (this._branchDepth >= WorkflowBuilderImpl.MAX_BRANCH_DEPTH) {
			throw new Error(
				`Maximum branch depth (${WorkflowBuilderImpl.MAX_BRANCH_DEPTH}) exceeded while building workflow graph`,
			);
		}
		this._branchDepth++;
		try {
			return this._addBranchToGraphInner(nodes, branch, nameMapping);
		} finally {
			this._branchDepth--;
		}
	}

	private _addBranchToGraphInner(
		nodes: Map<string, GraphNode>,
		branch: NodeInstance<string, string, unknown>,
		nameMapping?: Map<string, string>,
	): string {
		// Create nameMapping if not passed (tracks node ID -> actual map key for renamed nodes)
		const effectiveNameMapping = nameMapping ?? new Map<string, string>();

View on GitHub (pinned to 5ac6606e81)