pydantic/monty · error
checked Ready above
Error message
checked Ready above
What it means
In `handle_repl_feed` (crates/monty-proto/src/worker.rs:522) the worker asserts the session state is `Ready` via `unreachable!("checked Ready above")` after having already matched on the state earlier in the same request. The `mem::replace` pattern swaps the state out to `Configured(None)` and pattern-matches; if the state is anything but `Ready` here the worker panics. This is a child-side internal invariant: the earlier check and this extraction are expected to agree.
Source
Thrown at crates/monty-proto/src/worker.rs:522
fn handle_repl_feed(&mut self, feed: pb::Feed, sink: &mut dyn EventSink) -> pb::ChildEvent {
if let Err(event) = self.ensure_repl() {
return *event;
}
if !matches!(self.state, SessionState::Ready(_)) {
// ensure_repl left it un-Ready only when mid-suspension
return protocol_violation("Feed without a session ready for input");
}
if !feed.skip_type_check
&& let Some(event) = self.type_check_feed(&feed.code)
{
return event;
}
let inputs = match named_inputs(feed.inputs) {
Ok(inputs) => inputs,
Err(event) => return *event,
};
let SessionState::Ready(mut repl) = mem::replace(&mut self.state, SessionState::Configured(None)) else {
unreachable!("checked Ready above");
};
// The working directory persists in the REPL (including `os.chdir`);
// the parent sends one only to switch it, and an older parent never does.
if !feed.cwd.is_empty() {
repl.set_cwd(&feed.cwd);
}
// snippets fed with skip_type_check never become type-check context:
// the caller explicitly excluded them from checking, so later snippets
// must not be checked against their (unchecked) bindings either
if !feed.skip_type_check
&& let Some(state) = &mut self.type_check
{
state.pending_snippet = Some(feed.code.clone());
}
let mut print = ProtoPrint::new(sink, self.print_flush_interval);
let result = repl.feed_start(&feed.code, inputs, PrintWriter::Callback(&mut print));
let event = self.drive(result, &mut print);
print.drain();View on GitHub (pinned to adc986b362)
Solutions
- Read the surrounding `handle_repl_feed` code and restore the invariant: an `is Ready` check must immediately precede the `mem::replace` extraction with no intervening state mutation.
- Replace the fragile double-check with a single extraction that returns `protocol_violation(...)` instead of panicking on mismatch.
- Run the monty-proto worker tests (`cargo test -p monty-proto`) to confirm the REPL feed/resume alternation still holds.
- If seen in a deployed worker, upgrade/replace the worker: a panic here crashes the subprocess and the pool discards it.
Example fix
// before
let SessionState::Ready(mut repl) = mem::replace(&mut self.state, SessionState::Configured(None)) else {
unreachable!("checked Ready above");
};
// after
let SessionState::Ready(mut repl) = mem::replace(&mut self.state, SessionState::Configured(None)) else {
return protocol_violation("feed requires a Ready session");
}; Defensive patterns
Strategy: validation
Validate before calling
// Parent: only feed while the session is ready
if (sessionState !== 'ready') throw new Error('feed requires a ready session'); Type guard
fn is_ready(s: &SessionState) -> bool { matches!(s, SessionState::Ready(_)) } Try / catch
match worker_result { Ok(ev) => .., Err(e) if e.is_crash() => pool.replace_worker(), } Prevention
- Keep the state check and mem::replace extraction adjacent — no mutation between them.
- Prefer protocol_violation returns over unreachable! in the child so bad frames cannot crash workers.
- Test the feed/resume alternation explicitly in monty-proto worker tests.
When it happens
Trigger: A `ReplFeed` request handled by `handle` → `handle_repl_feed` where the state was `Ready` at the first check but the second `let SessionState::Ready(...) = mem::replace(...)` fails — only possible if the state machine was mutated between the two checks or the earlier check was removed/changed during refactoring.
Common situations: Protocol state-machine refactors in monty-proto; adding a new request kind that mutates `self.state` without updating the check order; a compromised/buggy child implementation diverging from the documented alternation contract.
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 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/bed28518ca0de387.
Report an issue: GitHub.