eyaltoledano/claude-task-master · error

Cannot create circular dependency: task

Error message

Cannot create circular dependency: task ${parentIdNum} is already a subtask or dependent of task ${existingTaskIdNum}

What it means

addSubtask uses isTaskDependentOn to detect whether attaching the existing task under the parent would create a cycle: if the parent is already a subtask of, or depends on, the task being converted, the hierarchy would become circular. The error names both IDs to help untangle the dependency graph.

Solutions

  1. Inspect the dependency graph of both tasks (task-master shows dependencies) to find the cycle.
  2. Remove the offending dependency or subtask relationship first, then retry the conversion.
  3. Choose a different parent task that does not depend on the candidate child.
  4. Restructure so the hierarchy is acyclic (e.g. convert the parent instead).

Example fix

// before
await addSubtask('2', '1'); // task 2 depends on task 1 → cycle
// after
// remove task 2's dependency on task 1 first, then:
await addSubtask('2', '1');
Defensive patterns

Strategy: validation

Validate before calling

const data = readJSON(tasksPath, projectRoot, tag);
const parent = data.tasks.find(t => t.id === parseInt(parentId, 10));
const child = data.tasks.find(t => t.id === parseInt(existingTaskId, 10));
if (parent && child && isTaskDependentOn(data.tasks, parent, child.id)) {
  throw new Error(`Cycle: task ${parent.id} already depends on task ${child.id}`);
}

Type guard

function createsCycle(parent, childId, deps = parent?.dependencies || []) {
  return deps.includes(childId) || parent?.parentTaskId === childId;
}

Try / catch

try {
  await addSubtask(tasksPath, parentId, existingTaskId);
} catch (err) {
  if (err.message.startsWith('Cannot create circular dependency')) {
    // remove the offending dependency or pick a different parent
  } else throw err;
}

Prevention

When it happens

Trigger: Calling addSubtask(parentId, existingTaskId) where parentId is a subtask/dependent of existingTaskId — e.g. converting task A into a subtask of task B when B already depends on A.

Common situations: Complex dependency graphs built up over time, scripting batch conversions without cycle analysis, or users reorganizing tasks without visualizing dependencies.

Related errors


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/c601d7c99be59fc3. Report an issue: GitHub.

Appendix: source

Thrown at scripts/modules/task-manager/add-subtask.js:79

			const existingTask = data.tasks[existingTaskIndex];

			// Check if task is already a subtask
			if (existingTask.parentTaskId) {
				throw new Error(
					`Task ${existingTaskIdNum} is already a subtask of task ${existingTask.parentTaskId}`
				);
			}

			// Check for circular dependency
			if (existingTaskIdNum === parentIdNum) {
				throw new Error(`Cannot make a task a subtask of itself`);
			}

			// Check if parent task is a subtask of the task we're converting
			// This would create a circular dependency
			if (isTaskDependentOn(data.tasks, parentTask, existingTaskIdNum)) {
				throw new Error(
					`Cannot create circular dependency: task ${parentIdNum} is already a subtask or dependent of task ${existingTaskIdNum}`
				);
			}

			// Find the highest subtask ID to determine the next ID
			const highestSubtaskId =
				parentTask.subtasks.length > 0
					? Math.max(...parentTask.subtasks.map((st) => st.id))
					: 0;
			const newSubtaskId = highestSubtaskId + 1;

			// Clone the existing task to be converted to a subtask
			newSubtask = {
				...existingTask,
				id: newSubtaskId,
				parentTaskId: parentIdNum
			};

View on GitHub (pinned to c0c98d367c)