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

  1. 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.
  2. Break the cycle by updating the offending row's parentId to null (root) or the correct parent.
  3. Restore affected flow model records from a clean backup if the graph is extensively corrupted.
  4. If chains are legitimately deep, check/raise MAX_FLOW_MODEL_ANCESTORS in allow-list.ts, but only after confirming no cycle exists.
  5. 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

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


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/05b6f6e2c28481cc. Report an issue: GitHub.