pydantic/monty · error · Error
${what} expected event ${kind}, got ${event.tag}
Error message
${what} expected event ${kind}, got ${event.tag} What it means
After sending a control request, `control()` verifies the reply's event tag matches the expected kind (e.g. `create-result`, `dump-result`, `finish-result`). A mismatch means the worker answered, but with the wrong kind of event — a protocol/schema inconsistency between the JS transport and the compiled wasm component. The message includes both the expected and actual tag for diagnosis.
Source
Thrown at crates/monty-js/ts/worker/transport.ts:314
turn = aborted ? this.toTurn(aborted) : crashed('worker exited without a turn-ending event')
// the component answers an abort with an error, never a suspension;
// servicing one would let a compromised worker call the host past
// the budget, so it ends the worker instead
if (turn.kind !== 'error' && turn.kind !== 'crashed') {
this.dead = true
turn = { kind: 'protocol', message: `worker answered abort-feed with ${turn.kind}` }
}
}
}
if (turn.kind === 'crashed') this.dead = true
return turn
}
/** Sends a control request and verifies its expected event kind. */
private async control(request: ComponentRequest, kind: ComponentEvent['tag'], what: string): Promise<ComponentEvent> {
const event = await this.run(request, undefined)
if (!event) throw new Error(`${what} produced no turn-ending event (worker crashed)`)
if (event.tag !== kind) throw new Error(`${what} expected event ${kind}, got ${event.tag}`)
return event
}
/** Runs one turn, forwarding buffered prints and retaining its terminator. */
private async run(request: ComponentRequest, onPrint: OnPrint | undefined): Promise<ComponentEvent | null> {
let events: ComponentEvent[]
try {
const result = await this.dispatcher(request)
if (result.status === 'shutdown') this.dead = true
// the component reports the limit in force (the configured one, else
// the 1000 default; a dump's on load), so it is adopted from the reply
if (request.tag === 'configure' || request.tag === 'load') {
this.suspensionLimit = result.maxSuspensions
this.suspensionsSeen = 0n
}
events = result.events
} catch {
return nullView on GitHub (pinned to adc986b362)
Solutions
- Rebuild and redeploy the wasm component together with the TS layer (`make build-wasm`, then rebuild the JS bundle) so all protocol artifacts come from one source.
- Never hand-edit `crates/monty-js/ts/worker/component/` files — they are generated; regenerate them with the build.
- Ensure each session processes turns to completion (await each `feedRun`/`dump`/`finish`) so no stale events remain queued before the next control request.
- Pin matching versions of `@pydantic/monty` and the compiled component in your deployment.
Example fix
// before // stale component + new TS bundle -> tag mismatch const bytes = await session.dump() // after make build-wasm # regenerate component + WIT-derived declarations make build-js # rebuild TS layer against them const bytes = await session.dump()
Defensive patterns
Strategy: try-catch
Try / catch
try {
result = await session.finish()
} catch (err) {
if (err instanceof Error && /expected event .*, got /.test(err.message)) {
// version skew: rebuild component + bindings, then retry on a fresh session
session = await pool.checkout()
} else throw err
} Prevention
- Keep the wasm component, WIT declarations, and TS layer from one build.
- Pin matching package + component versions in deployments.
- Always await each turn to completion so no stale events queue.
- Regenerate (never hand-edit) ts/worker/component files.
When it happens
Trigger: A control request receives an event whose `tag` differs from `kind` — typically caused by version skew between the checked-in WIT declarations, the compiled component, and the TS `ts/worker` layer, or by a worker emitting a terminator event from a previous unfinished turn.
Common situations: Hand-editing or regenerating only part of the component bindings; deploying a cached old wasm module behind an updated JS bundle; reusing a session whose previous turn ended abnormally so the next control call consumes a stale event.
Related errors
- Dump returned an unexpected event
- Monty.create could not auto-load the monty wasm module in th
- the wasm worker does not support filesystem mounts (browser
- ${what} produced no turn-ending event (worker crashed)
- worker reported unknown pending call id ${id}
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/84fdee96e318029b.
Report an issue: GitHub.