pydantic/monty · error
checked above
Error message
checked above
What it means
In `handle_resume_call` (crates/monty-proto/src/worker.rs:573) the `NotHandled` branch asserts the session is `Suspended` with `unreachable!("checked above")`. An earlier guard on `self.state` should have established `SessionState::Suspended`; if the state is not suspended when a `NotHandled` result arrives, the worker panics rather than returning a protocol violation.
Source
Thrown at crates/monty-proto/src/worker.rs:573
let Some(call_id) = expected_call_id else {
return protocol_violation("ResumeCall without a suspended function/OS call");
};
if resume.call_id != call_id {
return protocol_violation(&format!(
"ResumeCall call_id {} does not match {call_id}",
resume.call_id
));
}
let Some(wire_result) = resume.result else {
return protocol_violation("ResumeCall has no result");
};
// NotHandled resolves against the suspended call itself — the child
// owns the no-handler semantics (`OsFunctionCall::on_no_handler`), so
// the parent never has to compute or echo the default exception.
let result: ExtFunctionResult =
if matches!(wire_result.kind, Some(pb::ext_function_result::Kind::NotHandled(_))) {
let SessionState::Suspended(progress) = &self.state else {
unreachable!("checked above");
};
let ReplProgress::OsCall(call) = progress.as_ref() else {
return protocol_violation("NotHandled is only valid answering a suspended OS call");
};
ExtFunctionResult::Error(call.function_call.on_no_handler())
} else {
match wire_result.try_into() {
Ok(result) => result,
Err(err) => return protocol_violation(&format!("invalid result: {err}")),
}
};
let SessionState::Suspended(progress) = mem::replace(&mut self.state, SessionState::Configured(None)) else {
unreachable!("checked above");
};
let mut print = ProtoPrint::new(sink, self.print_flush_interval);
let outcome = match *progress {
ReplProgress::FunctionCall(call) => call.resume(result, PrintWriter::Callback(&mut print)),
ReplProgress::OsCall(call) => call.resume(result, PrintWriter::Callback(&mut print)),View on GitHub (pinned to adc986b362)
Solutions
- Ensure the parent sends at most one resume per suspension and only while the child reported a suspended progress event.
- In worker.rs, collapse the check: perform the `Suspended` extraction once and return `protocol_violation("resume requires a suspended session")` on mismatch instead of panicking.
- Check git history around line 573 for a refactor that separated the guard from the unreachable and reunify them.
- Replace the worker if this fires in production — the pool will detect the crashed child and respawn it.
Example fix
// before
let SessionState::Suspended(progress) = &self.state else { unreachable!("checked above") };
// after
let SessionState::Suspended(progress) = &self.state else {
return protocol_violation("NotHandled requires a suspended session");
}; Defensive patterns
Strategy: validation
Validate before calling
// Parent: answer each suspension at most once
if (!suspended || alreadyResumed) throw new Error('no pending suspension to answer with NotHandled'); Type guard
const isSuspended = (s: SessionState): s is { kind: 'Suspended' } => s.kind === 'Suspended'; Try / catch
try { await session.resume({ notHandled: true }) } catch (e) { if (e instanceof MontyCrashedError) pool.replace(); } Prevention
- Track suspension state on the parent side; never resume twice.
- Return protocol_violation from the child instead of unreachable! for wrong-state frames.
- Add tests for NotHandled answered outside a suspended OS call.
When it happens
Trigger: A `ResumeCall` request with `kind = NotHandled` arriving while `self.state` is not `SessionState::Suspended` — e.g. resuming a session that was never suspended, or whose suspension was already consumed by a previous resume.
Common situations: A buggy parent sending two resumes for one suspension; protocol refactors that moved or removed the earlier `Suspended` check; replaying a request against a session in the wrong phase.
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
- checked Ready above
- snapshot has already been resumed
- ${what} produced no turn-ending event (worker crashed)
- task coroutine_id doesn't point to a Coroutine
- task has no frames and no coroutine_id
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/c78a8a222c09529a.
Report an issue: GitHub.