n8n-io/n8n · error
invalid_graph
invalid_graph
Error message
invalid_graph
What it means
Returned as HTTP 400 {error:'invalid_graph', reason:...} when startExecution.start() throws GraphValidationError. validateExecutableGraph() throws this class for graphs that can NEVER run: zero trigger nodes, more than one trigger node, a slot index that is not a non-negative integer, or a slot index above MAX_SLOT_INDEX (100). Unlike UnimplementedError (501), these are hard structural defects, not missing features.
Source
Thrown at packages/@n8n/engine/src/server/routes/workflow-executions.ts:75
const parsed = StartExecutionBody.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({
error: 'invalid_request',
details: parsed.error.flatten(),
});
return;
}
try {
const result = await startExecution.start(parsed.data);
res.status(201).json(result);
} catch (error) {
if (error instanceof AdmittanceRejectedError) {
res.status(429).json({ error: 'admittance_rejected', reason: error.reason });
return;
}
if (error instanceof GraphValidationError) {
res.status(400).json({ error: 'invalid_graph', reason: error.message });
return;
}
if (error instanceof UnimplementedError) {
res.status(501).json({ error: 'unimplemented', reason: error.message });
return;
}
throw error;
}
});
return router;
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure exactly one node has type 'trigger'.
- Ensure every edge.outputIndex and edge.inputIndex is an integer in [0, 100].
- Read response.body.reason — it names the failing rule and the offending edge/node.
- Run validateExecutableGraph() (or an equivalent local check) before POSTing during development.
Example fix
// before — no trigger node, invalid slot index
graph = {
nodes: [{ id: 'n1', name: 'Do', type: 'v1-node' }],
edges: [{ from: 'n1', to: 'n1', outputIndex: -1, inputIndex: 0 }]
};
// after — one trigger node, non-negative integer indices
graph = {
nodes: [
{ id: 't', name: 'Start', type: 'trigger' },
{ id: 'n1', name: 'Do', type: 'v1-node' }
],
edges: [{ from: 't', to: 'n1', outputIndex: 0, inputIndex: 0 }]
}; Defensive patterns
Strategy: validation
Validate before calling
const MAX_SLOT_INDEX = 100;
function assertValidGraphStructure(graph) {
const triggers = graph.nodes.filter(n => n.type === 'trigger');
if (triggers.length !== 1) throw new Error(`Expected exactly 1 trigger, got ${triggers.length}`);
for (const e of graph.edges) {
for (const idx of [e.outputIndex ?? 0, e.inputIndex ?? 0]) {
if (!Number.isInteger(idx) || idx < 0 || idx > MAX_SLOT_INDEX) {
throw new Error(`Bad slot index ${idx} on edge ${e.from}->${e.to}`);
}
}
}
} Type guard
function isStructurallyValidGraph(graph) {
const triggers = graph.nodes.filter(n => n.type === 'trigger');
if (triggers.length !== 1) return false;
return graph.edges.every(e =>
[e.outputIndex ?? 0, e.inputIndex ?? 0].every(i => Number.isInteger(i) && i >= 0 && i <= 100)
);
} Try / catch
const res = await fetch('/workflow-executions', { method: 'POST', body: JSON.stringify(payload) });
if (res.status === 400) {
const body = await res.json();
if (body.error === 'invalid_graph') {
// body.reason names the exact structural rule that failed
}
} Prevention
- Always include exactly one node of type 'trigger'.
- Keep every edge index an integer in [0, 100].
- Run assertValidGraphStructure(graph) locally during development.
- Read body.reason on a 400 invalid_graph to pinpoint the offending node/edge.
When it happens
Trigger: POST /workflow-executions with a graph that has no node of type 'trigger'; a graph with two 'trigger' nodes; an edge whose outputIndex or inputIndex is a float, negative, or greater than 100. The reason field echoes the exact GraphValidationError message.
Common situations: Building a graph programmatically and forgetting the trigger node. Duplicating a trigger node during composition. Sending connection indices derived from a zero-based vs one-based mismatch. Sending a placeholder index like 999 that exceeds MAX_SLOT_INDEX.
Related errors
- invalid_request
- unimplemented
- invalid_graph
- Edge ${edge.from} → ${edge.to} has slot index ${index}; slot
- Edge ${edge.from} → ${edge.to} leaves output slot ${edge.out
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b9b8b7c2b6c58e65.
Report an issue: GitHub.