nocobase/nocobase · error · Error
Invalid FlowModel ancestor chain
Error message
Invalid FlowModel ancestor chain
What it means
loadFlowModelAncestors walks up the FlowModel node tree from a starting node, collecting ancestors by repeatedly fetching parentId. It throws 'Invalid FlowModel ancestor chain' when the chain is cyclic (a parentId already seen) or when the chain exceeds MAX_FLOW_MODEL_ANCESTORS. This guards the server against infinite loops / runaway recursion caused by corrupted node graphs.
Source
Thrown at packages/plugins/@nocobase/plugin-flow-engine/src/server/variables/allow-list.ts:278
new Map<string, FlowModelChildCacheValue>();
state[cacheKey] = cache;
const childCacheKey = JSON.stringify([parentUid, subKey]);
if (cache.has(childCacheKey)) return (await cache.get(childCacheKey)) || null;
const load = getFlowModelRepository(ctx).findModelNodeSnapshotByParentId(parentUid, { subKey });
cache.set(childCacheKey, load);
const child = await load;
cache.set(childCacheKey, child);
return child;
}
async function loadFlowModelAncestors(ctx: ResourcerContext, currentNode: FlowModelNodeSnapshot) {
const ancestors: FlowModelNodeSnapshot[] = [];
const seen = new Set([currentNode.uid]);
let parentId = currentNode.parentId;
while (parentId) {
if (seen.has(parentId) || ancestors.length >= MAX_FLOW_MODEL_ANCESTORS) {
throw new Error('Invalid FlowModel ancestor chain');
}
seen.add(parentId);
const parent = await getFlowModelNode(ctx, parentId);
if (!parent) throw new Error('Missing FlowModel ancestor');
ancestors.push(parent);
parentId = parent.parentId;
}
return Object.freeze(ancestors);
}
async function loadFlowModelAncestorUids(ctx: ResourcerContext, currentNode: FlowModelNodeSnapshot) {
try {
return new Set((await loadFlowModelAncestors(ctx, currentNode)).map((ancestor) => ancestor.uid));
} catch {
return null;
}
}
View on GitHub (pinned to fa42722fef)
Solutions
- Inspect the flow model rows for the offending node: SELECT uid, parent_id FROM flow_models and trace parent links to find the cycle or self-reference.
- Break the cycle by updating the offending row's parentId to null (root) or the correct parent.
- Restore affected flow model records from a clean backup if the graph is extensively corrupted.
- If chains are legitimately deep, check/raise MAX_FLOW_MODEL_ANCESTORS in allow-list.ts, but only after confirming no cycle exists.
- Fix the client/import path that produced the bad re-parenting to prevent recurrence.
Example fix
// before (bad data) // node-1.parentId = 'node-2'; node-2.parentId = 'node-1' // after // UPDATE flow_models SET "parentId" = NULL WHERE uid = 'node-2';
Defensive patterns
Strategy: validation
Validate before calling
// before building the allow-list, detect cycles client-side
function hasCycle(node, getNodeId, getParentId, max = 32) {
const seen = new Set();
let p = getParentId(node);
while (p) {
if (seen.has(p) || seen.size >= max) return true;
seen.add(p);
p = getParentId({ parentId: p });
}
return false;
} Type guard
const isFiniteChain = (chain: {uid: string; parentId: string | null}[]): boolean => {
const seen = new Set<string>();
for (const n of chain) {
if (seen.has(n.uid)) return false;
seen.add(n.uid);
}
return chain.length <= 32;
}; Try / catch
try {
const ancestors = await loadFlowModelAncestors(ctx, node);
} catch (e) {
if (e.message === 'Invalid FlowModel ancestor chain') {
// flag the flow model as corrupted and surface an admin-facing message
}
throw e;
} Prevention
- Never re-parent flow model nodes with raw SQL without checking for cycles.
- Add a unique constraint/cleanup so deleting a node updates children's parentId.
- Keep ancestor chains shallow; restructure deeply nested flows.
- Validate parent links after importing/exporting flow definitions.
When it happens
Trigger: A FlowModel node's parentId points back to an ancestor already visited (cycle, e.g. node A.parent=B, B.parent=A), or the ancestor chain is longer than MAX_FLOW_MODEL_ANCESTORS. Called via loadFlowModelAncestorUids or createRecordSlotCompilerOptions when building variable allow-lists for a record slot.
Common situations: Corrupted flow_models rows after a partial import/export or manual DB edit; data migration bugs that re-parent nodes without breaking old links; duplicate node UIDs restored from a backup; a bug in flow design client writing a self-referencing parentId.
Related errors
- flowModels:move source and target must be sibling nodes unde
- Missing FlowModel ancestor
- Invalid FlowModel lineage
- No rows selected for deletion.
- [flow-engine] snippet not found: ${ref}
AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01).
Data as JSON: /api/errors/05b6f6e2c28481cc.
Report an issue: GitHub.