{"record":{"id":"66b8a8b4d351e0c2","repo":"mastra-ai/mastra","slug":"this-relationship-would-create-a-cycle","errorCode":null,"errorMessage":"This relationship would create a cycle.","messagePattern":"This relationship would create a cycle\\.","errorType":"exception","errorClass":"WorkItemRelationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/storage/domains/work-items/base.ts","lineNumber":605,"sourceCode":"  readonly code = 'invalid_work_item_relation';\n}\n\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,","sourceCodeStart":587,"sourceCodeEnd":623,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/storage/domains/work-items/base.ts#L587-L623","documentation":"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.","triggerScenarios":"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).","commonSituations":"Reorganizing hierarchies in a UI tree (drag parent onto child), bulk imports with rotated parent links, or merging items without re-checking lineage.","solutions":["Re-parent the descendant subtree first (set the child's parent to X's current parent), then move X under it","Before updating, walk the proposed parent's ancestor chain client-side to confirm it doesn't contain itemId","Catch WorkItemRelationError and reject the move with a clear message"],"exampleFix":"// before\nawait workItems.update({ id: parentId, parentWorkItemId: childId }); // cycle\n// after\nawait workItems.update({ id: childId, parentWorkItemId: grandparentId }); // detach subtree first\nawait workItems.update({ id: parentId, parentWorkItemId: childId });","handlingStrategy":"validation","validationCode":"function wouldCycle(item, parent, byId) {\n  let cursor = byId.get(parent);\n  while (cursor?.parentWorkItemId) {\n    if (cursor.parentWorkItemId === item) return true;\n    cursor = byId.get(cursor.parentWorkItemId);\n  }\n  return false;\n}","typeGuard":"null","tryCatchPattern":"try {\n  await workItems.update({ id, parentWorkItemId });\n} catch (e) {\n  if (e instanceof WorkItemRelationError && e.message.includes('create a cycle')) {\n    // offer subtree re-parent flow instead\n  } else throw e;\n}","preventionTips":["Walk the proposed parent's ancestor chain in the UI before allowing a re-parent","For drag-and-drop trees, disallow dropping a node into its own subtree","Re-parent descendants first when reorganizing hierarchies"],"tags":["storage","work-items","cycle","hierarchy"],"backgroundTag":"cyclic-reference-detected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}