{"record":{"id":"13d0cd389b170602","repo":"n8n-io/n8n","slug":"plan-contains-duplicate-task-ids","errorCode":null,"errorMessage":"Plan contains duplicate task IDs","messagePattern":"Plan contains duplicate task IDs","errorType":"validation","errorClass":"PlanValidationError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/src/planned-tasks/planned-task-service.ts","lineNumber":32,"sourceCode":" * IDs, unknown deps, missing checkpoint deps, dependency cycles, etc.).\n * Callers — notably `plan.tool.ts` — should catch this specifically and surface\n * the message back to the LLM so it can retry with a corrected graph. Storage,\n * abort, or programming errors are NOT this class and must propagate.\n */\nexport class PlanValidationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'PlanValidationError';\n\t}\n}\n\nfunction hasDuplicateIds(tasks: PlannedTask[]): boolean {\n\treturn new Set(tasks.map((task) => task.id)).size !== tasks.length;\n}\n\nfunction validateDependencies(tasks: PlannedTask[]): void {\n\tif (hasDuplicateIds(tasks)) {\n\t\tthrow new PlanValidationError('Plan contains duplicate task IDs');\n\t}\n\n\tconst knownIds = new Set(tasks.map((task) => task.id));\n\tconst byId = new Map(tasks.map((task) => [task.id, task]));\n\tfor (const task of tasks) {\n\t\tfor (const depId of task.deps) {\n\t\t\tif (!knownIds.has(depId)) {\n\t\t\t\tthrow new PlanValidationError(`Task \"${task.id}\" depends on unknown task \"${depId}\"`);\n\t\t\t}\n\t\t}\n\t\tif (task.kind === 'checkpoint') {\n\t\t\tif (task.deps.length === 0) {\n\t\t\t\tthrow new PlanValidationError(\n\t\t\t\t\t`Checkpoint task \"${task.id}\" must depend on at least one build-workflow task`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst dependsOnBuildWorkflow = task.deps.some(\n\t\t\t\t(depId) => byId.get(depId)?.kind === 'build-workflow',","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/src/planned-tasks/planned-task-service.ts#L14-L50","documentation":"Thrown as a PlanValidationError by validateDependencies (planned-task-service.ts:31-32) when two or more tasks in the submitted graph share the same id. It fires before any dependency or cycle checks, so a duplicate-id graph never reaches scheduling. The class docstring notes that plan.tool.ts catches this specifically and returns the message to the LLM as a tool result for retry.","triggerScenarios":"createPlan is called with a tasks array where new Set(tasks.map(t => t.id)).size < tasks.length. The LLM (or a programmatic caller) reused an id across two build-workflow or checkpoint tasks.","commonSituations":"LLM generates ids like 'task-1' twice; a programmatic plan builder concatenates two sub-plans without id namespacing; copy-paste of a task template without regenerating the id.","solutions":["Give each task a globally unique id (e.g. prefix with a namespace or use a UUID).","Run a dedupe pass on tasks before calling createPlan: const ids = new Set(); tasks.filter(t => !ids.has(t.id) && ids.add(t.id)).","If surfaced to the LLM, follow the tool's guidance and re-call with corrected ids."],"exampleFix":"// before: [{ id: 't1', ... }, { id: 't1', ... }]\n// after:  [{ id: 't1', ... }, { id: 't2', ... }]","handlingStrategy":"validation","validationCode":"function hasDuplicateIds(tasks: { id: string }[]): boolean {\n  return new Set(tasks.map(t => t.id)).size !== tasks.length;\n}\nif (hasDuplicateIds(tasks)) throw new Error('Plan contains duplicate task IDs');","typeGuard":"function hasUniqueIds(tasks: { id: string }[]): boolean {\n  return new Set(tasks.map(t => t.id)).size === tasks.length;\n}","tryCatchPattern":"import { PlanValidationError } from './planned-task-service';\ntry { await coordinator.createPlan(threadId, tasks, meta); }\ncatch (e) {\n  if (e instanceof PlanValidationError) {\n    // return e.message to the LLM as a tool result for retry (as plan.tool.ts does)\n  }\n  throw e;\n}","preventionTips":["Generate ids with a UUID or namespaced counter.","Dedupe tasks by id before calling createPlan.","Let the LLM retry on PlanValidationError rather than treating it as fatal."],"tags":["planned-tasks","validation","plan-graph","llm-retryable"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}