pydantic/monty · critical
current task is in unexpected Completed state after resolvin
Error message
current task is in unexpected Completed state after resolving futures: {:?} What it means
After the host resolves pending futures, resume_with_resolved_futures re-examines the current task's state; a Completed state here means the task suspended even though it had already finished, which the scheduler never intends to do. It panics with the task's debug state. Internal scheduler invariant, not user-triggerable.
Source
Thrown at crates/monty/src/bytecode/vm/async_exec.rs:1022
match task.state {
TaskState::Failed(_) => {
// Current task failed - resume with exception so it can be caught by
// surrounding `try/except`.
let TaskState::Failed(err) = mem::replace(&mut task.state, TaskState::Ready) else {
unreachable!();
};
return self.resume_with_exception(err);
}
TaskState::Blocked(_) => {
// Current task is still blocked on unresolved futures.
}
TaskState::Ready => {
self.scheduler.remove_from_ready_queue(current_task_id);
return self.run_external();
}
TaskState::Completed(_) => {
// Should never have suspended if the task was completed
panic!(
"current task is in unexpected Completed state after resolving futures: {:?}",
task.state
);
}
}
}
// Current task was not able to resume, but there might be other ready tasks which can make
// progress
if let Some(next_task_id) = self.scheduler.next_ready_task() {
self.save_current_context();
self.scheduler.set_current_task(Some(next_task_id));
self.load_or_init_task(next_task_id)?;
return self.run_external();
}
let pending_call_ids = self.get_pending_call_ids();
View on GitHub (pinned to adc986b362)
Solutions
- Ensure every path that completes a task also removes it from pending resolution delivery
- Check handle_task_completion's waiter-handoff branch does not leave the completed task as current
- Assert the task is not Completed before registering it for future resolutions
- Report with a repro if triggered by plain asyncio-style code
Defensive patterns
Strategy: fallback
Prevention
- Complete and dequeue a task in one step, never leaving it current-and-completed
- Check pending-resolution registration happens before any completion path
- Re-run the scheduler's resume tests after completion-path changes
When it happens
Trigger: The current task is marked Completed while parked awaiting external-future resolution — i.e. some path completed the task without removing it from the pending-await bookkeeping before resolutions were processed.
Common situations: Hit during development on task completion vs. suspension ordering in async_exec.rs.
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
- task coroutine_id doesn't point to a Coroutine
- task has no frames and no coroutine_id
- pending_externals entry doesn't point to an ExternalFuture
- Awaiter::GatherSlot gather id is not a GatherFuture
- resolve_child called on a gather that was never awaited
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/598c008ef7df75da.
Report an issue: GitHub.