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
- 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'.
- 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.
- If a renamed/reshaped tool: update WORKFLOW_BUILD_TOOLS and the source extraction logic in buildSeedWorkflows to the new tool name / I/O shape.
- 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
- Keep the harness @n8n/workflow-sdk version aligned with the builder that produced the trace.
- Maintain WORKFLOW_BUILD_TOOLS as builder tool names change.
- Log skipReason entries even on partial success to catch drift early.
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
- Thread ${ref.threadId} reconstructed to zero seed messages b
- No runs for thread ${ref.threadId} in LangSmith project "${s
- Thread ${ref.threadId}: the live turn is the first/only user
- A case declares ${String(byName.size)} distinct scenario see
- Scenario "${scenario.name}" declares seed table "${table.nam
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b7962a6dbce6ce84.
Report an issue: GitHub.