pydantic/monty · critical
Awaiter::GatherSlot gather id is not a GatherFuture
Error message
Awaiter::GatherSlot gather id is not a GatherFuture
What it means
When a child task of asyncio.gather completes successfully, deliver_awaiter_success reads the Awaiter::GatherSlot's gather id expecting a GatherFuture heap entry; any other type panics. This means the gather's id in the awaiter chain is stale or corrupt — normally impossible because the gather holds the awaiter alive.
Source
Thrown at crates/monty/src/bytecode/vm/async_exec.rs:847
/// optionally switch VM context into `task_id` (calling
/// `remove_from_ready_queue` first since `deliver_value_to_task`
/// already queued it).
/// - `None` if the chain was consumed by an intermediate gather that's
/// still in flight, or if the terminal task is gone (in which case
/// the value is dropped).
fn deliver_awaiter_success(&mut self, mut awaiter: Awaiter, mut value: Value) -> Option<TaskId> {
let this = self;
loop {
match awaiter {
Awaiter::Task(t) => {
this.deliver_value_to_task(t, value);
return Some(t);
}
Awaiter::GatherSlot { gather, source } => {
let gather_val = Value::Ref(gather);
defer_drop!(gather_val, this);
let HeapReadOutput::GatherFuture(mut outer) = this.heap.read(gather) else {
panic!("Awaiter::GatherSlot gather id is not a GatherFuture")
};
let success = outer.resolve_child(this, source, value)?;
awaiter = success.awaiter;
value = Value::Ref(success.list_id);
}
}
}
}
/// Walks the awaiter chain starting at `awaiter`, tearing each
/// intermediate gather down with `error`, and fails the terminal task.
///
/// Returns:
/// - `Some(task_id)` if failure reached a live task — the caller may
/// optionally switch VM context into it; the task's state is already
/// `Failed(error)` so `resume_with_resolved_futures`'s post-loop check
/// will raise the exception when control returns. (Callers that need
/// the task in `Ready` instead — `handle_task_failure` — shouldView on GitHub (pinned to adc986b362)
Solutions
- Verify the GatherFuture is kept referenced while any child task holds a GatherSlot awaiter
- Check py_dec_ref_ids on GatherFuture releases child awaiters only at destruction
- Assert the heap type when creating the GatherSlot awaiter
- Report upstream with a repro if hit unmodified
Defensive patterns
Strategy: fallback
Prevention
- Keep GatherFuture alive while any child holds a GatherSlot
- Route gather reference release through py_dec_ref_ids
- Run gather-focused async tests after touching awaiter chaining
When it happens
Trigger: A gather child task finishes and the stored gather HeapId resolves to a non-GatherFuture entry, typically after the gather was freed early and the heap slot reused.
Common situations: Hit during development on gather internals (resolve_child, Awaiter chaining) or heap lifetime changes.
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
- resolve_child called on a gather that was never awaited
- gather commit frame id is not a GatherFuture
- gather item is not a Coroutine, ExternalFuture, or GatherFut
- 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/5300fe2707806710.
Report an issue: GitHub.