n8n-io/n8n · critical · Error
Unhandled seed mode: ${JSON.stringify(unhandled)}
Error message
Unhandled seed mode: ${JSON.stringify(unhandled)} What it means
Thrown in the seed-resolution switch when config.seed matches none of the handled arms ('thread', 'inline', undefined). The `const unhandled: never = config.seed` assignment is an exhaustiveness check: it compiles only if every member of the seed discriminated union is cased above. At runtime it means a new seed mode was added to the union (or the schema) without a matching case — a harness/framework bug, never a fixture or config mistake. The whole block is wrapped so the throw is re-tagged as 'Seeding failed'.
Source
Thrown at packages/@n8n/instance-ai/evaluations/harness/build-workflow.ts:395
: reconstructed.sourceProject;
logger.info(
` Reconstructed seed from thread ${config.seed.threadId}: ${String(reconstructed.runCount)} runs → ${String(seed.messages.length)} message(s), ${String(seed.workflows.length)} workflow(s)${contSuffix} [${wsLabel}]${config.laneTag ?? ''}`,
);
break;
}
case 'inline':
// The arm is a superset of the restore payload — `mode` is the case
// schema's discriminant and never reaches restore-thread.
seed = config.seed;
break;
case undefined:
break;
default: {
// A new arm must decide what to restore here; without this the case
// would silently run UNSEEDED, which is the failure this slot exists
// to make impossible.
const unhandled: never = config.seed;
throw new Error(`Unhandled seed mode: ${JSON.stringify(unhandled)}`);
}
}
} catch (error: unknown) {
// A seed that can't be resolved is a harness/framework problem, not an
// agent build failure — tag it and fail before spending a live turn.
seedingFailed = true;
throw new Error(`Seeding failed: ${error instanceof Error ? error.message : String(error)}`);
}
const openingMessage = conversation[0]?.text ?? '';
const isMultiTurn = isMultiTurnConversation(conversation);
logger.info(
` Running case${isMultiTurn ? ' [multi-turn]' : ''}: "${truncate(openingMessage, 60)}"${config.laneTag ?? ''}`,
);
const projectId = await client.getPersonalProjectId();
await client.ensureThread(threadId, projectId);
View on GitHub (pinned to 5ac6606e81)
Solutions
- Add a `case` for the new seed mode in the switch and resolve `seed` from it before the default.
- Ensure the Zod schema strictly rejects unknown modes (no `.passthrough()` on the discriminant).
- Keep the `never` exhaustiveness assignment so the compiler flags the missing case at build time.
Example fix
// before
case 'inline': seed = config.seed; break;
case undefined: break;
default: { const unhandled: never = config.seed; throw new Error(`Unhandled seed mode: ...`); }
// after (new 'snapshot' arm added to the union)
case 'snapshot': seed = await loadSnapshotSeed(config.seed); break;
case 'inline': seed = config.seed; break;
case undefined: break;
default: { const unhandled: never = config.seed; throw new Error(`Unhandled seed mode: ...`); } Defensive patterns
Strategy: type-guard
Type guard
// The existing exhaustiveness guard IS the type-level defense. Keep `never`.
function resolveSeed(config: BuildConfig): ConversationSeed | undefined {
switch (config.seed?.mode) {
case 'thread': /* ... */ return seed;
case 'inline': return config.seed;
case undefined: return undefined;
default: {
const _exhaustive: never = config.seed;
throw new Error(`Unhandled seed mode: ${JSON.stringify(_exhaustive)}`);
}
}
} Prevention
- Always case the `never` default so the compiler flags a missing arm when the union widens.
- Make the Zod schema strict (no `.passthrough()` on the discriminant) so unknown modes fail at parse.
- Land handler arms and schema arms in the same commit to avoid the window where this throw is reachable.
When it happens
Trigger: A developer extends the seed config schema with a new discriminant arm (e.g. 'snapshot') and adds it to the union type without adding a case here. The exhaustiveness `never` is the compile-time guard; reaching the throw means the type was bypassed (e.g. `as`, loose schema) or the case was omitted.
Common situations: Mid-migration: the schema/type arm lands before the handler arm; a discriminated union widened without updating all switches; Zod `.passthrough()` letting an unmodeled mode through.
Related errors
- Seeding failed: ${error instanceof Error ? error.message : S
- no artifact handler registered for type: ${type}
- Pre-seeding created ${String(dataTableIds.length)} data tabl
- The opening turn attaches seeded workflow "${attachedSeedWor
- Invalid sandbox provider "${String(exhaustiveProvider)}". Se
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/6deb07c0b609c532.
Report an issue: GitHub.