n8n-io/n8n · error · Error

Thread ${threadId}: ${buildSignalIds.size} workflow(s) were

Error message

Thread ${threadId}: ${buildSignalIds.size} workflow(s) were built in the trace but reconstruction recovered 0${sdkVersion ? ` (trace built with @n8n/workflow-sdk ${sdkVersion})` : ''} — ${cause}. Details: ${details}. Fix: align the @n8n/workflow-sdk parser, or update reconstruction (WORKFLOW_BUILD_TOOLS / source extraction in buildSeedWorkflows).

What it means

Thrown when the trace contains build-signal tool runs (workflow build events) but buildSeedWorkflows recovered zero workflows. The message distinguishes two root causes: the recovered source was rejected by the @n8n/workflow-sdk parser (SDK subset/version drift, e.g. native JS like .join), or a build tool was renamed / its I/O shape changed. Skip reasons from reconstruction are included to point at the failing run IDs.

Source

Thrown at packages/@n8n/instance-ai/evaluations/harness/langsmith-seed.ts:771

			`[seed] Thread ${threadId}: ${skipped.length} built workflow(s) could not be reconstructed and were skipped: ${skipped
				.map((id) => `${id} (${skipReason.get(id) ?? 'unknown'})`)
				.join('; ')}`,
		);
	}

	// Builds happened but we recovered nothing → throw rather than silently seed 0
	// workflows (reported as a framework_issue; the message names the real cause).
	if (buildSignalIds.size > 0 && workflows.length === 0) {
		const details = [...skipReason.entries()].map(([id, why]) => `${id} → ${why}`).join(' | ');
		// Distinguish a parser rejection (SDK subset/version drift) from a missing or
		// renamed build tool — they point at different fixes.
		const sdkRejected = [...skipReason.values()].some((why) =>
			/not an allowed SDK method|Failed to parse workflow code/.test(why),
		);
		const cause = sdkRejected
			? "source was recovered but this harness's @n8n/workflow-sdk parser rejected it — SDK subset/version drift (the trace's builder accepted code the current parser forbids, e.g. native JS like `.join`)"
			: 'the build tool was likely renamed or its input/output shape changed (e.g. inline-code → filePath)';
		throw new Error(
			`Thread ${threadId}: ${buildSignalIds.size} workflow(s) were built in the trace but reconstruction recovered 0${sdkVersion ? ` (trace built with @n8n/workflow-sdk ${sdkVersion})` : ''} — ${cause}. Details: ${details}. Fix: align the @n8n/workflow-sdk parser, or update reconstruction (WORKFLOW_BUILD_TOOLS / source extraction in buildSeedWorkflows).`,
		);
	}
	if (workflows.length < buildSignalIds.size) {
		console.warn(
			`[seed] Thread ${threadId}: reconstructed ${workflows.length}/${buildSignalIds.size} built workflow(s) — partial; check for trace-shape drift if unexpected.`,
		);
	}

	return workflows;
}

type ParsedSeedWorkflow = {
	name?: string;
	nodes: Array<Record<string, unknown>>;
	connections: Record<string, unknown>;
};

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Read the `details` portion of the message (id → reason pairs) to see whether reasons mention 'not an allowed SDK method' or 'Failed to parse workflow code'.
  2. If parser rejection: align the harness's @n8n/workflow-sdk version with the trace's sdkVersion (also surfaced in the message), or relax the parser to accept the construct the trace used.
  3. If a renamed/reshaped tool: update WORKFLOW_BUILD_TOOLS and the source extraction logic in buildSeedWorkflows to the new tool name / I/O shape.
  4. Re-record the trace with the current builder if alignment is not feasible.

Example fix

// before — WORKFLOW_BUILD_TOOLS only lists the old name
const WORKFLOW_BUILD_TOOLS = ['inline-code'];

// after — add the renamed tool and update extraction
const WORKFLOW_BUILD_TOOLS = ['inline-code', 'filePath'];
// and update buildSeedWorkflows to read source from inputs.filePath when inputs.code is absent
Defensive patterns

Strategy: try-catch

Validate before calling

function workflowsWillRecover(buildSignalIds: Set<string>, skipReason: Map<string, string>): boolean {
  if (buildSignalIds.size === 0) return true; // nothing to recover
  // recoverable unless every signal was rejected by the parser
  return false;
}

// Better: pre-flight the parser on one recovered source
const sampleSource = await extractSourceForBuild(firstBuildToolRun);
const parsed = safeParseWorkflow(sampleSource);
if (!parsed.ok) {
  logger.warn('parser rejects recovered source; align SDK version before running the full case');
}

Type guard

null

Try / catch

try {
  const workflows = buildSeedWorkflows(workflowScanRuns, boundaryMs, threadId, sdkVersion);
} catch (e) {
  if (e instanceof Error && /were built in the trace but reconstruction recovered 0/.test(e.message)) {
    // inspect e.message details, align parser or WORKFLOW_BUILD_TOOLS, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: The trace's builder accepted workflow code (e.g. used .join or other native JS) that the current harness @n8n/workflow-sdk parser forbids; a build tool was renamed (e.g. inline-code → filePath); the tool's input/output schema changed so source extraction returns nothing; WORKFLOW_BUILD_TOOLS list is stale and no longer matches the build tool names in the trace.

Common situations: Upgrading @n8n/workflow-sdk tightened the parser and now rejects previously valid code; the workflow builder changed how it emits build events; replaying a trace recorded with a newer builder than the harness supports.

Related errors


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