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
- 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
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
- 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
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
- The related work item chain contains a cycle.
- Related work item not found in this project.
- A work item cannot relate to itself.
- AcpAgent does not support resuming suspended generate calls
- AcpAgent does not support resuming suspended stream calls
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/66b8a8b4d351e0c2.
Report an issue: GitHub.