{"record":{"id":"62c45850510750f4","repo":"n8n-io/n8n","slug":"invalid-graph","errorCode":"invalid_graph","errorMessage":"Graph has no trigger node to start from","messagePattern":"Graph has no trigger node to start from","errorType":"validation","errorClass":"GraphValidationError","httpStatus":400,"severity":"error","filePath":"packages/@n8n/engine/src/graph/validate-executable-graph.ts","lineNumber":25,"sourceCode":"export class GraphValidationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'GraphValidationError';\n\t}\n}\n\n/**\n * Asserts the graph is one the engine is willing to execute, before any state\n * 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`,","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/engine/src/graph/validate-executable-graph.ts#L7-L43","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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'."],"exampleFix":"// before\nconst graph = { nodes: [actionNode], edges: [] };\nawait startExecution.start({ graph, workflowId });\n\n// after\nconst graph = { nodes: [triggerNode, actionNode], edges: [{ from: triggerNode.id, to: actionNode.id, outputIndex: 0, inputIndex: 0, isBackEdge: false }] };\nawait startExecution.start({ graph, workflowId });","handlingStrategy":"validation","validationCode":"function hasTrigger(graph: WorkflowGraph): boolean {\n  return graph.nodes.some(n => n.type === 'trigger');\n}\nif (!hasTrigger(graph)) { // do not submit for execution }","typeGuard":"function hasExactlyOneTrigger(graph: WorkflowGraph): boolean {\n  return graph.nodes.filter(n => n.type === 'trigger').length === 1;\n}","tryCatchPattern":"try {\n  validateExecutableGraph(graph);\n} catch (err) {\n  if (err instanceof GraphValidationError && /no trigger node/.test(err.message)) {\n    // prompt user to add a trigger\n  } else throw err;\n}","preventionTips":["Validate the graph structure client-side before submit.","Always carry the trigger node when building from templates.","Use the type string 'trigger' exactly."],"tags":["graph","validation","trigger","engine"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}