pydantic/monty · critical
resolve_child called on a gather that was never awaited
Error message
resolve_child called on a gather that was never awaited
What it means
A GatherFuture tracks whether it has been awaited; resolve_child is called when a child finishes and requires the gather to be in the Awaited state. Pending means the child finished before the gather was ever awaited, which the spawn/await ordering should prevent, so it panics. Completed/Failed gathers are tolerated (child result discarded), only Pending is fatal.
Source
Thrown at crates/monty/src/bytecode/vm/async_exec.rs:1191
/// `VM::handle_task_failure` for in-frame exceptions). Both settle the
/// gather before any other sibling has a chance to resolve, and the
/// siblings then keep running: their results arrive here afterwards and
/// are dropped.
///
/// Returns `None` while children are still in flight, or if the gather has
/// already settled; otherwise `Some(GatherSuccess)` with the cached result
/// list.
fn resolve_child(&mut self, vm: &mut VM<'h>, child_id: HeapId, value: Value) -> Option<GatherSuccess> {
// A sibling failed (or the commit pass rolled back) while this child
// was still running: it has a result, and nowhere for it to go.
// `Pending` is not reachable — a child only exists once awaited.
match &self.get(vm.heap).state {
GatherState::Awaited(_) => {}
GatherState::Completed(_) | GatherState::Failed(_) => {
value.drop_with(vm.heap);
return None;
}
GatherState::Pending => panic!("resolve_child called on a gather that was never awaited"),
}
// Remove this child's slot-index mapping.
let indices: SmallVec<[usize; 1]> = self
.get_mut(vm.heap)
.as_awaited_mut()
.expect("resolve_child called on non-Awaited gather")
.pending_children
.remove(&child_id)
.expect("resolve_child: child not registered with this gather");
// Take `results` out so the writes can do their clones (which need
// `&Heap` access) without fighting the `&mut`-chain that
// `as_awaited_mut` requires. We put it back into the gather right
// after, before the completion scan.
let mut results = mem::take(
&mut self
.get_mut(vm.heap)View on GitHub (pinned to adc986b362)
Solutions
- Ensure gather's await path transitions state to Awaited before children can ever complete — or before spawning them
- Verify resolve_child is only reachable through awaiters installed by the Awaited transition
- Check that Pending gathers never install GatherSlot awaiters on children
- Report the repro upstream if produced by ordinary asyncio.gather usage
Defensive patterns
Strategy: fallback
Prevention
- Await the gather (entering Awaited state) as the only path that installs child awaiters
- Never let children signal completion while the gather is Pending
- Cover gather with ordering tests: child completes before/after await
When it happens
Trigger: A gather child task resolves while the GatherFuture is still Pending — e.g. the children were spawned and ran to completion before the gather object's __await__ registered the Awaited state, or an awaiter was attached to children without awaiting the gather itself.
Common situations: Hit when modifying gather's await path (exec_get_awaitable / resolve_child wiring) or when adding a way for children to start before the gather is awaited.
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
- Awaiter::GatherSlot gather id is not a GatherFuture
- 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/5b193069f84b2f80.
Report an issue: GitHub.