pydantic/monty · error · ProtocolError
worker reported ResolveFutures with no pending call ids
Error message
worker reported ResolveFutures with no pending call ids
What it means
The worker sent a `ResolveFutures` turn with an empty `pendingCallIds` list, which is meaningless — the turn exists only to report pending futures. The host throws `ProtocolError` because the protocol requires at least one pending call id; this signals a worker bug or version mismatch.
Source
Thrown at crates/monty-js/ts/session.ts:716
},
)
this.futures.set(callId, future)
}
/**
* Every sandbox task is blocked: wait until at least one pending future
* settles, then deliver everything that is ready.
*/
private async answerResolveFutures(event: ResolveFuturesTurn, onPrint: PrintCallback): Promise<object> {
const pending = event.pendingCallIds.map((id) => {
const future = this.futures.get(id)
if (future === undefined) {
throw new ProtocolError(`worker reported unknown pending call id ${id}`)
}
return future
})
if (pending.length === 0) {
throw new ProtocolError('worker reported ResolveFutures with no pending call ids')
}
await Promise.race(pending.map((f) => f.settled))
const results: NativeFutureResult[] = pending
.filter((f) => f.done)
.map((f) => {
this.futures.delete(f.callId)
const outcome = f.outcome!
if ('ok' in outcome) {
// a prepare rejection is delivered as the future's error, matching
// how a sync return value's conversion failure surfaces
try {
return { callId: f.callId, ok: true, value: prepare(outcome.ok, this.instances) }
} catch (err) {
const { excType, message } = jsErrorParts(err)
return { callId: f.callId, ok: false, excType, message }
}
}
const { excType, message } = jsErrorParts(outcome.err)View on GitHub (pinned to adc986b362)
Solutions
- Align the monty worker binary with the installed @pydantic/monty package version
- Re-run the workload on a fresh session; the pool replaces misbehaving workers
- Report a bug with reproduction if a matched version still produces it
Defensive patterns
Strategy: try-catch
Try / catch
try {
await session.feedRun(code, { externalLookup })
} catch (e) {
if (e instanceof ProtocolError && e.message.includes('no pending call ids')) {
session = await pool.checkout() // worker desync; get a fresh one
} else throw e
} Prevention
- Use a matched worker binary and JS package version
- Report occurrences with the turn trace — an empty ResolveFutures is always a worker bug
- Discard the session after ProtocolError; the pool's crash replacement handles worker health
When it happens
Trigger: A worker (wrong version, buggy, or compromised) emitting `ResolveFutures` with no ids after an external-call turn; host and worker disagreeing about whether futures are outstanding.
Common situations: Mixed worker/binding versions after an upgrade, custom worker builds, or protocol regressions.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- worker reported unknown pending call id ${id}
- Dump returned an unexpected event
- ClassInstance expects an object instance
- notCallableMessage(method)
- ClassInstance expects an instance of a class, not a null-pro
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/e5c0352e0e46733b.
Report an issue: GitHub.