n8n-io/n8n · error · Error
Thread ${ref.threadId} reconstructed to zero seed messages b
Error message
Thread ${ref.threadId} reconstructed to zero seed messages before the live turn — the trace shape may have drifted (expected root runs named 'turn' with inputs.message / outputs.response). What it means
Thrown after buildSeedMessages returns an empty array despite prior turns existing. It means the LangSmith trace's structure does not match what the reconstruction expects: root runs named 'turn' carrying inputs.message and outputs.response. The harness prefers failing loudly over silently seeding an empty conversation.
Source
Thrown at packages/@n8n/instance-ai/evaluations/harness/langsmith-seed.ts:399
"agent_role=message_turn root id; verify it matches the name==='turn' root id.)",
);
} else {
liveIndex = idx;
}
}
if (liveIndex < 1) {
throw new Error(
`Thread ${ref.threadId}: the live turn is the first/only user turn — no prior turn to seed. ` +
'Pin a later turn, or use a plain conversation case.',
);
}
const liveTurnRun = userTurns[liveIndex];
const boundaryMs = new Date(liveTurnRun.start_time ?? NaN).getTime();
const liveTurn = userMessageOf(liveTurnRun)!;
const messages = buildSeedMessages(rootRuns, toolRuns, boundaryMs);
if (messages.length === 0) {
throw new Error(
`Thread ${ref.threadId} reconstructed to zero seed messages before the live turn — the trace shape may have drifted (expected root runs named 'turn' with inputs.message / outputs.response).`,
);
}
const sdkVersion = rootRuns
.map((r) => asString(metadata(r).workflow_sdk_version))
.find((v) => v !== undefined);
const workflows = buildSeedWorkflows(workflowScanRuns, boundaryMs, ref.threadId, sdkVersion);
const dataTables = buildSeedDataTables(toolRuns, boundaryMs);
return {
seed: {
source: { kind: 'langsmith', threadId: ref.threadId, sourceProject },
messages,
workflows,
dataTables,
// A trace carries no agent artifacts yet; only authored seeds can seed one.
agents: [],
},View on GitHub (pinned to 5ac6606e81)
Solutions
- Inspect the trace: list root runs for the thread and check their `name` field and inputs/outputs keys against the expected 'turn' / inputs.message / outputs.response shape.
- If the shape drifted, update buildSeedMessages to match the new run naming and I/O keys.
- Re-record the trace with the current SDK so the shape matches the harness's expectations.
- Confirm userMessageOf and response extraction helpers still find their fields on the new run shape.
Example fix
// before — buildSeedMessages expects runs named 'turn' with inputs.message
const messages = buildSeedMessages(rootRuns, toolRuns, boundaryMs);
// after — log the actual shape, then align the helper
console.dir(rootRuns.map(r => ({ name: r.name, inputs: Object.keys(r.inputs ?? {}), outputs: Object.keys(r.outputs ?? {}) })));
// e.g. update buildSeedMessages to filter `r.name === 'message_turn'` if that is the new name Defensive patterns
Strategy: validation
Validate before calling
function traceShapeLooksExpected(rootRuns: { name?: string; inputs?: unknown; outputs?: unknown }[]): boolean {
if (!rootRuns.length) return false;
return rootRuns.every((r) =>
r.name === 'turn' &&
!!r.inputs && typeof r.inputs === 'object' && 'message' in (r.inputs as object) &&
!!r.outputs && typeof r.outputs === 'object' && 'response' in (r.outputs as object),
);
}
if (!traceShapeLooksExpected(rootRuns)) {
throw new Error('trace shape does not match reconstruction expectations; inspect rootRuns');
} Type guard
null
Try / catch
try {
const result = buildSeedFromLangsmith(client, ref, sourceProject, logger);
} catch (e) {
if (e instanceof Error && /reconstructed to zero seed messages/.test(e.message)) {
logger.error('trace shape drift detected', { rootRunsSummary: rootRuns.map(r => r.name) });
// re-record the trace or update buildSeedMessages
}
throw e;
} Prevention
- Pin the @n8n/workflow-sdk version the harness expects in lockfile.
- Add a schema assertion over root run shapes before reconstruction.
- Re-record eval traces after any SDK upgrade that changes run naming.
When it happens
Trigger: Root runs in the trace are no longer named 'turn'; inputs.message / outputs.response keys have been renamed by an SDK change; the trace was recorded by a different SDK version with a different run shape; nested/parent_run_id structure changed so root filtering yields nothing.
Common situations: Upgrading @n8n/workflow-sdk or LangTracer changed run naming conventions; replaying a trace recorded by an older agent runtime; LangSmith run enrichment altered the payload shape.
Related errors
- Thread ${threadId}: ${buildSignalIds.size} workflow(s) were
- 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/e18bca53a5642c2a.
Report an issue: GitHub.