n8n-io/n8n · error · GraphValidationError

invalid_graph

invalid_graph

Error message

Graph has no trigger node to start from

What it means

GraphValidationError (code: invalid_graph) thrown by validateExecutableGraph when graph.nodes contains zero nodes of type 'trigger'. The engine requires exactly one trigger as the execution entry point; with none, the graph can never start. This is a structural, fail-fast check before any execution state is created.

Source

Thrown at packages/@n8n/engine/src/graph/validate-executable-graph.ts:25

export class GraphValidationError extends Error {
	constructor(message: string) {
		super(message);
		this.name = 'GraphValidationError';
	}
}

/**
 * Asserts the graph is one the engine is willing to execute, before any state
 * 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`,

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Ensure the graph contains exactly one node with `type: 'trigger'` before submitting for execution.
  2. Validate the graph client-side: `graph.nodes.some(n => n.type === 'trigger')`.
  3. When building from a template, always carry the trigger node over; treat its absence as a build error.
  4. Double-check the node type string spelling — it must be exactly 'trigger'.

Example fix

// before
const graph = { nodes: [actionNode], edges: [] };
await startExecution.start({ graph, workflowId });

// after
const graph = { nodes: [triggerNode, actionNode], edges: [{ from: triggerNode.id, to: actionNode.id, outputIndex: 0, inputIndex: 0, isBackEdge: false }] };
await startExecution.start({ graph, workflowId });
Defensive patterns

Strategy: validation

Validate before calling

function hasTrigger(graph: WorkflowGraph): boolean {
  return graph.nodes.some(n => n.type === 'trigger');
}
if (!hasTrigger(graph)) { // do not submit for execution }

Type guard

function hasExactlyOneTrigger(graph: WorkflowGraph): boolean {
  return graph.nodes.filter(n => n.type === 'trigger').length === 1;
}

Try / catch

try {
  validateExecutableGraph(graph);
} catch (err) {
  if (err instanceof GraphValidationError && /no trigger node/.test(err.message)) {
    // prompt user to add a trigger
  } else throw err;
}

Prevention

When it happens

Trigger: Submitting a StartExecutionRequest whose graph has only regular/action nodes and no trigger; a trigger node mislabeled with a type other than 'trigger'; a graph built from a partial/template that omitted the trigger.

Common situations: Programmatic graph construction that adds action nodes but forgets the trigger; a deserialization bug stripping the trigger; templates exported without their trigger; UI flows that allow 'run' on an incomplete graph.

Related errors


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