n8n-io/n8n · error · TypeORMError
Cyclic dependency: ${JSON.stringify(node)}
Error message
Cyclic dependency: ${JSON.stringify(node)} What it means
`SubjectTopoligicalSorter` orders subjects for insert/update so parents are persisted before children. It performs a DFS over the dependency edges; if a node appears in its own predecessor chain (a true cycle), TypeORM cannot order the work and throws `Cyclic dependency: <node>`. Unlike the schema-level CircularRelationsError, this fires at flush time on the actual subject graph.
Source
Thrown at packages/@n8n/typeorm/src/persistence/SubjectTopoligicalSorter.ts:177
if (res.indexOf(edge[0]) < 0) res.push(edge[0]);
if (res.indexOf(edge[1]) < 0) res.push(edge[1]);
}
return res;
}
const nodes = uniqueNodes(edges);
let cursor = nodes.length,
sorted = new Array(cursor),
visited: any = {},
i = cursor;
while (i--) {
if (!visited[i]) visit(nodes[i], i, []);
}
function visit(node: any, i: number, predecessors: any[]) {
if (predecessors.indexOf(node) >= 0) {
throw new TypeORMError('Cyclic dependency: ' + JSON.stringify(node)); // todo: better error
}
if (!~nodes.indexOf(node)) {
throw new TypeORMError(
'Found unknown node. Make sure to provided all involved nodes. Unknown node: ' +
JSON.stringify(node),
);
}
if (visited[i]) return;
visited[i] = true;
// outgoing edges
let outgoing = edges.filter(function (edge) {
return edge[0] === node;
});
if ((i = outgoing.length)) {
let preds = predecessors.concat(node);View on GitHub (pinned to 5ac6606e81)
Solutions
- Make at least one side of the cycle nullable, insert with null, then UPDATE the FK in a second pass.
- Save in dependency order: persist and reload the parent, set its id on the child, then save the child.
- Use deferred FK constraints (Postgres `DEFERRABLE`) and wrap the save in a transaction so the cycle resolves at commit.
Example fix
// before — circular mandatory FKs in one save
a.b = b; b.a = a;
await repo.save([a, b]); // Cyclic dependency
// after — nullable side + two-step
@Entity() class A { @ManyToOne(() => B, { nullable: true }) b: B | null; }
await repo.save(a); // a inserted, b null
a.b = b; await repo.save(b); // b inserted
await repo.save(a); // a updated with b's id Defensive patterns
Strategy: validation
Validate before calling
// Before a multi-entity save, detect circular non-nullable FKs in the working set
function hasCycle(nodes: string[], edges: [string, string][]): boolean {
const adj = new Map<string, string[]>(); nodes.forEach(n => adj.set(n, []));
edges.forEach(([a, b]) => adj.get(a)!.push(b));
const state = new Map<string, 'vis'|'done'>();
const dfs = (n: string): boolean => {
state.set(n, 'vis');
for (const nb of adj.get(n) ?? []) { if (state.get(nb) === 'vis') return true; if (!state.has(nb) && dfs(nb)) return true; }
state.set(n, 'done'); return false;
};
return nodes.some(n => !state.has(n) && dfs(n));
} Try / catch
try { await repo.save(graph); } catch (e) { if (e instanceof TypeORMError && /Cyclic dependency/) { /* split into ordered saves or make one FK nullable */ } throw e; } Prevention
- Make at least one side of any mutual FK nullable.
- Save parents first, set ids, then save children in ordered steps.
- Consider DEFERRABLE FK constraints inside a transaction on Postgres.
When it happens
Trigger: Persisting two or more new entities whose non-nullable foreign keys point at each other in the same `save()` call, so neither can be inserted first. A self-referencing mandatory relation (entity A must have a parent A) being inserted without a pre-existing root.
Common situations: Calling `repo.save([a, b])` where `a.ref = b` and `b.ref = a` and both FKs are non-nullable. Building a graph in memory and saving it in one pass with circular mandatory dependencies.
Related errors
- Found unknown node. Make sure to provided all involved nodes
- Circular relations detected: ${path}. To resolve this issue
- Cannot ${operation}, given value must be instance of entity
- Removed entity "${subject.metadata.name}" is also scheduled
- Internal error. Subject ${subject.metadata.targetName} must
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/4c2ec4603acee5d1.
Report an issue: GitHub.