mastra-ai/mastra · error · WorkItemRelationError

This relationship would create a cycle.

Error message

This relationship would create a cycle.

What it means

Attaching this parent would make the item an ancestor of itself: walking up from the proposed parent reaches itemId. validateParentRelation detects this and throws WorkItemRelationError to keep the parent hierarchy an acyclic tree.

Source

Thrown at mastracode/factory/src/storage/domains/work-items/base.ts:605

  readonly code = 'invalid_work_item_relation';
}

export function validateParentRelation(
  projectItems: WorkItemRow[],
  itemId: string | undefined,
  parentWorkItemId: string | null,
): void {
  if (parentWorkItemId === null) return;
  const byId = new Map(projectItems.map(item => [item.id, item]));
  const parent = byId.get(parentWorkItemId);
  if (!parent) throw new WorkItemRelationError('Related work item not found in this project.');
  if (itemId === parentWorkItemId) throw new WorkItemRelationError('A work item cannot relate to itself.');

  const visited = new Set<string>();
  let cursor: WorkItemRow | undefined = parent;
  while (cursor?.parentWorkItemId) {
    if (cursor.parentWorkItemId === itemId) {
      throw new WorkItemRelationError('This relationship would create a cycle.');
    }
    if (visited.has(cursor.id)) throw new WorkItemRelationError('The related work item chain contains a cycle.');
    visited.add(cursor.id);
    cursor = byId.get(cursor.parentWorkItemId);
  }
}

/**
 * Diff `oldStages` → `newStages` and return the updated history: exited stages
 * get `exitedAt` + `exitedBy` stamped on their open entry, entered stages get
 * a new entry.
 */
export function applyStageTransition(
  history: WorkItemStageEntry[],
  oldStages: WorkItemStage[],
  newStages: WorkItemStage[],
  by: string,
  now: Date,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-parent the descendant subtree first (set the child's parent to X's current parent), then move X under it
  2. Before updating, walk the proposed parent's ancestor chain client-side to confirm it doesn't contain itemId
  3. Catch WorkItemRelationError and reject the move with a clear message

Example fix

// before
await workItems.update({ id: parentId, parentWorkItemId: childId }); // cycle
// after
await workItems.update({ id: childId, parentWorkItemId: grandparentId }); // detach subtree first
await workItems.update({ id: parentId, parentWorkItemId: childId });
Defensive patterns

Strategy: validation

Validate before calling

function wouldCycle(item, parent, byId) {
  let cursor = byId.get(parent);
  while (cursor?.parentWorkItemId) {
    if (cursor.parentWorkItemId === item) return true;
    cursor = byId.get(cursor.parentWorkItemId);
  }
  return false;
}

Type guard

null

Try / catch

try {
  await workItems.update({ id, parentWorkItemId });
} catch (e) {
  if (e instanceof WorkItemRelationError && e.message.includes('create a cycle')) {
    // offer subtree re-parent flow instead
  } else throw e;
}

Prevention

When it happens

Trigger: Updating item X's parentWorkItemId to a descendant of X (e.g. moving a parent under its own child during a re-parent or drag-and-drop operation).

Common situations: Reorganizing hierarchies in a UI tree (drag parent onto child), bulk imports with rotated parent links, or merging items without re-checking lineage.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/66b8a8b4d351e0c2. Report an issue: GitHub.