mastra-ai/mastra · error · WorkItemRelationError

The related work item chain contains a cycle.

Error message

The related work item chain contains a cycle.

What it means

While walking up the parent chain from the proposed parent, validateParentRelation revisited an item it already visited, meaning the EXISTING data already contains a cycle unrelated to itemId. It throws WorkItemRelationError to signal corrupt/looping parent links.

Source

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

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,
): WorkItemStageEntry[] {
  const timestamp = now.toISOString();

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Find and repair the existing cycle in stored work items (set one link's parentWorkItemId to null) before retrying
  2. Add a data-integrity audit that walks all parent chains and reports loops
  3. Catch WorkItemRelationError, log the offending chain, and surface it to an admin rather than end users

Example fix

// before
await workItems.update({ id, parentWorkItemId: brokenParentId }); // parent chain already loops
// after
await workItems.update({ id: loopingItemId, parentWorkItemId: null }); // break existing cycle first
await workItems.update({ id, parentWorkItemId: brokenParentId });
Defensive patterns

Strategy: validation

Validate before calling

function detectExistingCycle(items) {
  for (const item of items) {
    const seen = new Set();
    let c = items.find(i => i.id === item.parentWorkItemId);
    while (c) {
      if (seen.has(c.id)) return c.id;
      seen.add(c.id);
      c = items.find(i => i.id === c.parentWorkItemId);
    }
  }
  return null;
}

Type guard

null

Try / catch

try {
  await workItems.update({ id, parentWorkItemId });
} catch (e) {
  if (e instanceof WorkItemRelationError && e.message.includes('chain contains a cycle')) {
    // run data repair on stored parent links before retrying
  } else throw e;
}

Prevention

When it happens

Trigger: Attempting any create/update with a parentWorkItemId whose existing ancestor chain is already looped (e.g. legacy data where A→B→A), regardless of the new link being valid.

Common situations: Databases seeded or migrated with inconsistent parent pointers, prior bugs that wrote cycles, or concurrent updates that created a loop before validation ran.

Related errors


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