nocobase/nocobase · error

flowModels:attach subKey '${subKey}' already exists on paren

Error message

flowModels:attach subKey '${subKey}' already exists on parent '${parentId}'

What it means

Thrown by FlowModelRepository.attach when the model being attached has subType 'object' and another child model already occupies the same subKey under the target parent. Object sub-models are keyed maps, so each subKey must be unique per parent; attaching a second model with the same key would silently overwrite the existing child, so the repository rejects it.

Source

Thrown at packages/plugins/@nocobase/plugin-flow-engine/src/server/repository.ts:1930

                            ON NodeInfo.descendant = TreeTable.descendant
                                AND NodeInfo.depth = 0
         WHERE TreeTable.depth = 1
           AND TreeTable.ancestor = :ancestor
           AND NodeInfo.type = :type
           AND TreeTable.descendant != :uid
         LIMIT 1`,
        {
          type: 'SELECT',
          replacements: {
            ancestor: parentId,
            type: subKey,
            uid: modelUid,
          },
          transaction,
        },
      );
      if (conflict?.length) {
        throw new Error(`flowModels:attach subKey '${subKey}' already exists on parent '${parentId}'`);
      }
    }

    const normalizePosition = (input: unknown): FlowModelAttachPosition => {
      if (!input) return 'last';
      if (input === 'first' || input === 'last') return input;
      if (typeof input === 'object') {
        const p = input as any;
        const type = p?.type;
        const target = String(p?.target || '').trim();
        if ((type === 'before' || type === 'after') && target) {
          return { type, target };
        }
      }
      throw new Error('flowModels:attach invalid position');
    };

    const position: FlowModelAttachPosition =

View on GitHub (pinned to fa42722fef)

Solutions

  1. Query the existing children of parentId first and pick an unused subKey, or delete/detach the conflicting child before re-attaching.
  2. If the model is being moved back to a former parent, ensure the previous attach's transaction completed/rolled back so stale tree rows were removed.
  3. Catch the error and surface it to the user as 'a child with this key already exists' rather than retrying blindly.

Example fix

// before
await repo.attach({ uid, parentId, subKey: 'items', subType: 'object' });
// after
const children = await repo.findChildren(parentId);
if (children.some((c) => c.subKey === 'items')) subKey = 'items-2';
await repo.attach({ uid, parentId, subKey, subType: 'object' });
Defensive patterns

Strategy: validation

Validate before calling

const children = await repo.findChildren(parentId);
if (subType === 'object' && children.some((c) => c.subKey === subKey && c.uid !== uid)) {
  throw new SkipAttachError(`subKey '${subKey}' taken`);
}
await repo.attach({ uid, parentId, subKey, subType });

Type guard

const isFreeSubKey = (c: { subKey: string; uid: string }[], subKey: string, uid: string) =>
  !c.some((x) => x.subKey === subKey && x.uid !== uid);

Try / catch

try {
  await repo.attach({ uid, parentId, subKey, subType: 'object' });
} catch (e) {
  if (String(e.message).startsWith('flowModels:attach subKey')) {
    notifyUser('A child with this key already exists here.');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling flowModels.attach (or the flowModels:attach server action) with subType='object' where a sibling row already exists at depth=1 under parentId with NodeInfo.type === subKey, and that sibling's uid differs from the model being attached.

Common situations: Re-attaching a moved model after a failed/partial cleanup left the old row in the tree table; two UI panels concurrently creating a child under the same well-known key like 'detail' or 'form'; copying a model definition without renaming its subKey.

Related errors


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