pydantic/monty · error

checked above

Error message

checked above

What it means

This `unreachable!()` in `MountTable`-backed session resume (`resume_from_mounts`) asserts that when a `MountCallOutcome::NotHandled` arrives, `self.pending` is still the `Pending::Call` variant holding the suspended `os_call`. The variant was checked earlier in the same method, so firing indicates the pending state was mutated between the check and this arm — a broken state machine invariant in `monty-pool`. It is unreachable for library users driving the pool through its public API.

Source

Thrown at crates/monty-pool/src/checkout.rs:806

                    Ok(obj) => ResumeValue::Return(obj),
                    Err(err) => ResumeValue::Error(err.into_exception()),
                };
                match self.resume(value, &mut *on_print).await {
                    // The result never reached the child (too large or too deep
                    // to encode), so the call is still suspended — answer it
                    // with that error instead, letting the sandbox raise a
                    // catchable exception rather than stranding the feed. Only
                    // a rejection before the frame is written leaves `pending`
                    // set, so this cannot catch a genuine sandbox exception.
                    Err(PoolError::Runtime(exc)) if self.pending.is_some() => {
                        self.resume(ResumeValue::Error(exc), on_print).await.map(Some)
                    }
                    other => other.map(Some),
                }
            }
            MountCallOutcome::NotHandled(call) => {
                let Some(Pending::Call { os_call, .. }) = &mut self.pending else {
                    unreachable!("checked above");
                };
                *os_call = Some(Box::new(call));
                Ok(None)
            }
        }
    }

    /// Answers a [`TurnEvent::NameLookup`] with a [`NameLookupResult`] (or a
    /// `MontyObject`, an `Option<MontyObject>` where `None` is `Undefined`, or
    /// a `MontyException` for `Error`): a value resolves the name; `Undefined`
    /// makes the sandbox raise `NameError` for a plain lookup, or
    /// `AttributeError` when the lookup carried an `object_id` (a lazy
    /// attribute on a host-backed object — a class instance or class type);
    /// `Error` raises the host's exception in the sandbox, bypassing
    /// `hasattr()` / `getattr()` defaults the way a raising property does.
    pub async fn resume_name_lookup(
        &mut self,
        result: impl Into<NameLookupResult>,

View on GitHub (pinned to adc986b362)

Solutions

  1. Re-read `self.pending` immediately before the `NotHandled` handling instead of relying on an earlier check
  2. Audit the method for any path that replaces or clears `self.pending` between the first match and this arm
  3. Replace the assertion with a logged internal-error / crashed-worker result so a latent bug cannot panic the parent pool
  4. Run `cargo test -p monty-pool` including crash/recovery tests

Example fix

// before
let Some(Pending::Call { os_call, .. }) = &mut self.pending else {
    unreachable!("checked above");
};
// after
let Some(Pending::Call { os_call, .. }) = &mut self.pending else {
    return Err(PoolError::internal(
        "pending turn is not a call when handling NotHandled"));
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Check pending state right before use, not earlier
if !matches!(self.pending, Some(Pending::Call { .. })) {
    return Err(PoolError::internal("pending turn is not a call"));
}

Type guard

fn pending_call(pending: &Option<Pending>) -> Option<&OsCall> {
    match pending {
        Some(Pending::Call { os_call, .. }) => os_call.as_ref(),
        _ => None,
    }
}

Try / catch

// Replace the panic with a recoverable internal error so the pool can replace the worker
let Some(Pending::Call { os_call, .. }) = &mut self.pending else {
    return Err(PoolError::internal("NotHandled with no pending call"));
};

Prevention

When it happens

Trigger: Only when code modifies `self.pending` (e.g. resolving/clearing a pending call) between the earlier `Pending::Call` check and the `NotHandled` arm, or when a new `Pending` variant is introduced without updating this method.

Common situations: Hit during development of `monty-pool`: adding new pending-turn kinds (timers, futures), refactoring the suspension/resume state machine, or reordering the outcome match arms.

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/978e37c1f364d424. Report an issue: GitHub.