n8n-io/n8n · error · PlanValidationError

Checkpoint task "${task.id}" must depend on at least one bui

Error message

Checkpoint task "${task.id}" must depend on at least one build-workflow task

What it means

Thrown as a PlanValidationError by validateDependencies (planned-task-service.ts:44-48) when a task with kind === 'checkpoint' has an empty deps array. Checkpoints represent user-approval gates and must gate at least one build-workflow task; a checkpoint with no deps has nothing to gate. This is the first of two checkpoint-dep checks (the second, error 515, requires at least one dep to be build-workflow kind).

Source

Thrown at packages/@n8n/instance-ai/src/planned-tasks/planned-task-service.ts:45

	return new Set(tasks.map((task) => task.id)).size !== tasks.length;
}

function validateDependencies(tasks: PlannedTask[]): void {
	if (hasDuplicateIds(tasks)) {
		throw new PlanValidationError('Plan contains duplicate task IDs');
	}

	const knownIds = new Set(tasks.map((task) => task.id));
	const byId = new Map(tasks.map((task) => [task.id, task]));
	for (const task of tasks) {
		for (const depId of task.deps) {
			if (!knownIds.has(depId)) {
				throw new PlanValidationError(`Task "${task.id}" depends on unknown task "${depId}"`);
			}
		}
		if (task.kind === 'checkpoint') {
			if (task.deps.length === 0) {
				throw new PlanValidationError(
					`Checkpoint task "${task.id}" must depend on at least one build-workflow task`,
				);
			}
			const dependsOnBuildWorkflow = task.deps.some(
				(depId) => byId.get(depId)?.kind === 'build-workflow',
			);
			if (!dependsOnBuildWorkflow) {
				throw new PlanValidationError(
					`Checkpoint task "${task.id}" must depend on at least one build-workflow task`,
				);
			}
		}
	}

	const visiting = new Set<string>();
	const visited = new Set<string>();

	const visit = (taskId: string) => {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Add at least one dep to the checkpoint, pointing at the build-workflow task(s) it should gate.
  2. Reconsider whether a checkpoint is needed at all — if there is nothing to gate, remove it.
  3. Re-call the plan tool with the corrected checkpoint deps.

Example fix

// before: { id: 'cp1', kind: 'checkpoint', deps: [] }
// after:  { id: 'cp1', kind: 'checkpoint', deps: ['build-1'] }
Defensive patterns

Strategy: validation

Validate before calling

for (const t of tasks) {
  if (t.kind === 'checkpoint' && t.deps.length === 0) {
    throw new Error(`Checkpoint "${t.id}" needs at least one dep`);
  }
}

Type guard

function checkpointsHaveDeps(tasks: { kind: string; deps: string[] }[]): boolean {
  return tasks.every(t => t.kind !== 'checkpoint' || t.deps.length > 0);
}

Try / catch

try { await coordinator.createPlan(threadId, tasks, meta); }
catch (e) {
  if (e instanceof PlanValidationError) {
    // surface to LLM for retry
  }
  throw e;
}

Prevention

When it happens

Trigger: A submitted plan includes { kind: 'checkpoint', deps: [] }. The empty-deps check runs before the kind check, so this fires even if non-build-workflow deps would otherwise be acceptable.

Common situations: LLM models a checkpoint as a standalone task; a programmatic builder creates a checkpoint template and forgets to wire deps; the plan was edited and deps were cleared.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/c0898e424603a52b. Report an issue: GitHub.