{"record":{"id":"660281f2bce8ecec","repo":"mastra-ai/mastra","slug":"the-related-work-item-chain-contains-a-cycle","errorCode":null,"errorMessage":"The related work item chain contains a cycle.","messagePattern":"The related work item chain contains a cycle\\.","errorType":"exception","errorClass":"WorkItemRelationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/storage/domains/work-items/base.ts","lineNumber":607,"sourceCode":"\nexport function validateParentRelation(\n  projectItems: WorkItemRow[],\n  itemId: string | undefined,\n  parentWorkItemId: string | null,\n): void {\n  if (parentWorkItemId === null) return;\n  const byId = new Map(projectItems.map(item => [item.id, item]));\n  const parent = byId.get(parentWorkItemId);\n  if (!parent) throw new WorkItemRelationError('Related work item not found in this project.');\n  if (itemId === parentWorkItemId) throw new WorkItemRelationError('A work item cannot relate to itself.');\n\n  const visited = new Set<string>();\n  let cursor: WorkItemRow | undefined = parent;\n  while (cursor?.parentWorkItemId) {\n    if (cursor.parentWorkItemId === itemId) {\n      throw new WorkItemRelationError('This relationship would create a cycle.');\n    }\n    if (visited.has(cursor.id)) throw new WorkItemRelationError('The related work item chain contains a cycle.');\n    visited.add(cursor.id);\n    cursor = byId.get(cursor.parentWorkItemId);\n  }\n}\n\n/**\n * Diff `oldStages` → `newStages` and return the updated history: exited stages\n * get `exitedAt` + `exitedBy` stamped on their open entry, entered stages get\n * a new entry.\n */\nexport function applyStageTransition(\n  history: WorkItemStageEntry[],\n  oldStages: WorkItemStage[],\n  newStages: WorkItemStage[],\n  by: string,\n  now: Date,\n): WorkItemStageEntry[] {\n  const timestamp = now.toISOString();","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/storage/domains/work-items/base.ts#L589-L625","documentation":"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.","triggerScenarios":"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.","commonSituations":"Databases seeded or migrated with inconsistent parent pointers, prior bugs that wrote cycles, or concurrent updates that created a loop before validation ran.","solutions":["Find and repair the existing cycle in stored work items (set one link's parentWorkItemId to null) before retrying","Add a data-integrity audit that walks all parent chains and reports loops","Catch WorkItemRelationError, log the offending chain, and surface it to an admin rather than end users"],"exampleFix":"// before\nawait workItems.update({ id, parentWorkItemId: brokenParentId }); // parent chain already loops\n// after\nawait workItems.update({ id: loopingItemId, parentWorkItemId: null }); // break existing cycle first\nawait workItems.update({ id, parentWorkItemId: brokenParentId });","handlingStrategy":"validation","validationCode":"function detectExistingCycle(items) {\n  for (const item of items) {\n    const seen = new Set();\n    let c = items.find(i => i.id === item.parentWorkItemId);\n    while (c) {\n      if (seen.has(c.id)) return c.id;\n      seen.add(c.id);\n      c = items.find(i => i.id === c.parentWorkItemId);\n    }\n  }\n  return null;\n}","typeGuard":"null","tryCatchPattern":"try {\n  await workItems.update({ id, parentWorkItemId });\n} catch (e) {\n  if (e instanceof WorkItemRelationError && e.message.includes('chain contains a cycle')) {\n    // run data repair on stored parent links before retrying\n  } else throw e;\n}","preventionTips":["Run periodic integrity audits over stored parent chains","Fix migrations/seeds to never write looped parent pointers","Break any detected cycle by nulling one link before new writes"],"tags":["storage","work-items","cycle","data-integrity"],"backgroundTag":"cyclic-reference-detected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}