{"record":{"id":"a363f7c356c95e41","repo":"n8n-io/n8n","slug":"plan-contains-a-dependency-cycle-involving-task","errorCode":null,"errorMessage":"Plan contains a dependency cycle involving \"${taskId}\"","messagePattern":"Plan contains a dependency cycle involving \"(.+?)\"","errorType":"validation","errorClass":"PlanValidationError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/src/planned-tasks/planned-task-service.ts","lineNumber":66,"sourceCode":"\t\t\t}\n\t\t\tconst dependsOnBuildWorkflow = task.deps.some(\n\t\t\t\t(depId) => byId.get(depId)?.kind === 'build-workflow',\n\t\t\t);\n\t\t\tif (!dependsOnBuildWorkflow) {\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}\n\t}\n\n\tconst visiting = new Set<string>();\n\tconst visited = new Set<string>();\n\n\tconst visit = (taskId: string) => {\n\t\tif (visited.has(taskId)) return;\n\t\tif (visiting.has(taskId)) {\n\t\t\tthrow new PlanValidationError(`Plan contains a dependency cycle involving \"${taskId}\"`);\n\t\t}\n\n\t\tvisiting.add(taskId);\n\t\tconst task = byId.get(taskId);\n\t\tfor (const depId of task?.deps ?? []) {\n\t\t\tvisit(depId);\n\t\t}\n\t\tvisiting.delete(taskId);\n\t\tvisited.add(taskId);\n\t};\n\n\tfor (const task of tasks) {\n\t\tvisit(task.id);\n\t}\n}\n\nfunction isSuccess(task: PlannedTaskRecord): boolean {\n\treturn task.status === 'succeeded';","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/src/planned-tasks/planned-task-service.ts#L48-L84","documentation":"Thrown as a PlanValidationError by the DFS cycle check in validateDependencies (planned-task-service.ts:63-67). The visit function marks nodes in a 'visiting' set during the current path; re-encountering a node already in 'visiting' means a cycle. The taskId in the message is the node at which the cycle was detected. This runs after duplicate-id, unknown-dep, and checkpoint checks, so the graph is otherwise well-formed.","triggerScenarios":"Any tasks[] where the deps edges form a cycle, e.g. A depends on B and B depends on A, or a longer loop A->B->C->A. The recursive visit walks deps edges.","commonSituations":"LLM generates mutually dependent tasks; a programmatic builder adds a back-reference; manual plan editing introduces a circular dependency; checkpoints gating each other transitively.","solutions":["Break the cycle by removing or reversing at least one dep edge.","Model the dependency as a sequence (linearize the order) instead of a loop.","If two tasks are truly co-dependent, merge them into a single task.","Re-call the plan tool with the acyclic graph."],"exampleFix":"// before: [{ id: 'a', deps: ['b'] }, { id: 'b', deps: ['a'] }]\n// after:  [{ id: 'a', deps: [] }, { id: 'b', deps: ['a'] }]","handlingStrategy":"validation","validationCode":"function hasCycle(tasks: { id: string; deps: string[] }[]): boolean {\n  const byId = new Map(tasks.map(t => [t.id, t]));\n  const visiting = new Set<string>(), visited = new Set<string>();\n  const visit = (id: string): boolean => {\n    if (visited.has(id)) return false;\n    if (visiting.has(id)) return true;\n    visiting.add(id);\n    for (const d of byId.get(id)?.deps ?? []) if (visit(d)) return true;\n    visiting.delete(id); visited.add(id);\n    return false;\n  };\n  return tasks.some(t => visit(t.id));\n}\nif (hasCycle(tasks)) throw new Error('Plan contains a dependency cycle');","typeGuard":"function isAcyclic(tasks: { id: string; deps: string[] }[]): boolean {\n  return !hasCycle(tasks);\n}","tryCatchPattern":"try { await coordinator.createPlan(threadId, tasks, meta); }\ncatch (e) {\n  if (e instanceof PlanValidationError && /cycle/.test(e.message)) {\n    // remove/reverse an edge and retry\n  }\n  throw e;\n}","preventionTips":["Topologically sort tasks before submission to confirm acyclicity.","Avoid back-references when wiring deps programmatically.","Merge truly co-dependent tasks into one."],"tags":["planned-tasks","validation","plan-graph","cycle-detection","llm-retryable"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}