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
- Ensure the graph contains exactly one node with `type: 'trigger'` before submitting for execution.
- Validate the graph client-side: `graph.nodes.some(n => n.type === 'trigger')`.
- When building from a template, always carry the trigger node over; treat its absence as a build error.
- 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
- Validate the graph structure client-side before submit.
- Always carry the trigger node when building from templates.
- Use the type string 'trigger' exactly.
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
- Edge ${edge.from} → ${edge.to} has slot index ${index}; slot
- unimplemented
- invalid_request
- invalid_graph
- Workflow ${formatWorkflow(dbWorkflow)} has no node to start
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/62c45850510750f4.
Report an issue: GitHub.