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

  1. Ensure task completion removes the task from all scheduler queues before clearing frames/coroutine_id
  2. Check every Task constructor/spawn call site sets exactly one of frames or coroutine_id
  3. Add an assertion at task creation validating the frames-xor-coroutine_id invariant
  4. Report the reproduction upstream if hit without local modifications
Defensive patterns

Strategy: fallback

Prevention

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


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/b48c07888fac5f44. Report an issue: GitHub.