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

  1. Align the monty worker binary with the installed @pydantic/monty package version
  2. Re-run the workload on a fresh session; the pool replaces misbehaving workers
  3. 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

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


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/e5c0352e0e46733b. Report an issue: GitHub.