eyaltoledano/claude-task-master · error · MoveTaskError

TASK_ALREADY_EXISTS

TASK_ALREADY_EXISTS

Error message

Cannot move to existing task ID ${destTaskId}. Choose a different ID or use subtask destination.

What it means

This MoveTaskError with code TASK_ALREADY_EXISTS is thrown when moving a subtask out to become a standalone task, but the destination task ID is already occupied in the task list. The library refuses to overwrite or swap tasks implicitly during a subtask-to-task move, because doing so would silently destroy an existing task's data. It suggests either picking a free ID or, alternatively, moving the subtask to a different parent as a subtask destination.

Source

Thrown at scripts/modules/task-manager/move-task.js:339

	}

	// Find source subtask
	const sourceSubtaskIndex = sourceParentTask.subtasks.findIndex(
		(st) => st.id === sourceSubtaskId
	);
	if (sourceSubtaskIndex === -1) {
		throw new MoveTaskError(
			MOVE_ERROR_CODES.SUBTASK_NOT_FOUND,
			`Source subtask ${sourceId} not found`
		);
	}

	const sourceSubtask = sourceParentTask.subtasks[sourceSubtaskIndex];

	// Check if destination task exists
	const existingDestTask = tasks.find((t) => t.id === destTaskId);
	if (existingDestTask) {
		throw new MoveTaskError(
			MOVE_ERROR_CODES.TASK_ALREADY_EXISTS,
			`Cannot move to existing task ID ${destTaskId}. Choose a different ID or use subtask destination.`
		);
	}

	// Create new task from subtask
	const newTask = {
		id: destTaskId,
		title: sourceSubtask.title,
		description: sourceSubtask.description,
		status: sourceSubtask.status || 'pending',
		dependencies: sourceSubtask.dependencies || [],
		priority: sourceSubtask.priority || 'medium',
		details: sourceSubtask.details || '',
		testStrategy: sourceSubtask.testStrategy || '',
		subtasks: []
	};

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Pick a destination ID that does not exist: run `tasks.some(t => t.id === destId)` first and choose the next free ID.
  2. If the goal is to keep it nested, pass a subtask destination (parent.subsubtask form) instead of a task ID.
  3. List current tasks to find free IDs, then retry the move with a valid one.
  4. If overwriting is truly intended, delete or move the existing task at that ID first, then retry.
  5. If rerunning a migration, make it idempotent by skipping when the destination already exists.

Example fix

// before
moveTask(tasksPath, '5.2', '3'); // task 3 exists -> TASK_ALREADY_EXISTS
// after
const tasks = readJSON(tasksPath);
const nextId = Math.max(...tasks.map(t => t.id)) + 1;
moveTask(tasksPath, '5.2', String(nextId));
Defensive patterns

Strategy: validation

Validate before calling

const tasks = JSON.parse(fs.readFileSync(tasksPath, 'utf8')).master.tasks;
if (tasks.some(t => t.id === Number(destTaskId))) {
  throw new Error(`Destination ID ${destTaskId} is taken; pick a free ID.`);
}

Type guard

function isFreeTaskId(tasks, id) {
  return Number.isInteger(id) && !tasks.some(t => t.id === id);
}

Try / catch

try {
  await moveTask(tasksPath, '5.2', destId);
} catch (e) {
  if (e.code === 'TASK_ALREADY_EXISTS') {
    const freeId = Math.max(...getTasks().map(t => t.id)) + 1;
    await moveTask(tasksPath, '5.2', String(freeId));
  } else throw e;
}

Prevention

When it happens

Trigger: Calling moveTask (or moveTaskToTask via moveTask) with a source that is a subtask (sourceId like '5.2') and a numeric destination ID that already exists in tasks, e.g. moving subtask 5.2 to task ID 3 when task 3 exists.

Common situations: Auto-picking the destination ID from user input without checking availability; scripts that compute a target ID as max(existing)+1 but run on stale task data; running two move operations concurrently; rerunning a migration script that already created the destination task on a previous run.

Related errors


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