pydantic/monty · critical
task has no frames and no coroutine_id
Error message
task has no frames and no coroutine_id
What it means
load_or_init_task requires every scheduler task to carry either saved frames or a coroutine_id; a task with neither cannot be resumed. Hitting this panic means a task record was created or mutated into an impossible empty state. It is an interpreter-internal invariant, not reachable from sandboxed Python code.
Source
Thrown at crates/monty/src/bytecode/vm/async_exec.rs:699
// frames have already been saved, so route already-awaited failures
// through `handle_task_failure`, which restores the waiter before
// the error propagates.
let HeapReadOutput::Coroutine(coro) = self.heap.read(coro_id) else {
panic!("task coroutine_id doesn't point to a Coroutine")
};
let is_new = coro.get(self.heap).state == CoroutineState::New;
// Release the handle before either branch: both go on to drop
// references to this coroutine, and freeing it under a live
// reader panics.
drop(coro);
if is_new {
self.init_task_from_coroutine(coro_id)?;
} else {
return self.handle_task_failure(ExcType::cannot_reuse_already_awaited_coroutine());
}
} else {
// This shouldn't happen - task with no frames and no coroutine
panic!("task has no frames and no coroutine_id");
}
// Resolutions that landed while this task was parked already pushed
// their value onto `task.stack` (via `deliver_value_to_task` or
// `handle_task_completion`'s waiter-handoff branch), so the restored
// stack above is already in the post-AWAIT shape.
Ok(())
}
/// Initializes the VM state to run a coroutine for a spawned task.
///
/// Similar to exec_get_awaitable's coroutine handling, but for task initialization.
fn init_task_from_coroutine(&mut self, coroutine_id: HeapId) -> Result<(), RunError> {
let HeapReadOutput::Coroutine(mut coro) = self.heap.read(coroutine_id) else {
panic!("task coroutine_id doesn't point to a Coroutine")
};
View on GitHub (pinned to adc986b362)
Solutions
- Ensure task completion removes the task from all scheduler queues before clearing frames/coroutine_id
- Check every Task constructor/spawn call site sets exactly one of frames or coroutine_id
- Add an assertion at task creation validating the frames-xor-coroutine_id invariant
- Report the reproduction upstream if hit without local modifications
Defensive patterns
Strategy: fallback
Prevention
- Keep the invariant: every schedulable task has frames or a coroutine_id
- Remove tasks from queues atomically with state teardown
- Add a creation-time assertion for the frames-xor-coroutine_id invariant
When it happens
Trigger: A task in the scheduler has neither stack frames nor a coroutine_id when the scheduler tries to load it — e.g. task state was partially torn down but the task was left in the ready queue, or a Task was constructed without either field populated.
Common situations: Seen when modifying task teardown/completion paths in async_exec.rs so a Completed task remains schedulable, or when adding a new task-creation site that forgets to set coroutine_id.
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
- current task is in unexpected Completed state after resolvin
- 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/b48c07888fac5f44.
Report an issue: GitHub.