{"record":{"id":"5b193069f84b2f80","repo":"pydantic/monty","slug":"resolve-child-called-on-a-gather-that-was-never-awaited","errorCode":null,"errorMessage":"resolve_child called on a gather that was never awaited","messagePattern":"resolve_child called on a gather that was never awaited","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/monty/src/bytecode/vm/async_exec.rs","lineNumber":1191,"sourceCode":"    /// `VM::handle_task_failure` for in-frame exceptions). Both settle the\n    /// gather before any other sibling has a chance to resolve, and the\n    /// siblings then keep running: their results arrive here afterwards and\n    /// are dropped.\n    ///\n    /// Returns `None` while children are still in flight, or if the gather has\n    /// already settled; otherwise `Some(GatherSuccess)` with the cached result\n    /// list.\n    fn resolve_child(&mut self, vm: &mut VM<'h>, child_id: HeapId, value: Value) -> Option<GatherSuccess> {\n        // A sibling failed (or the commit pass rolled back) while this child\n        // was still running: it has a result, and nowhere for it to go.\n        // `Pending` is not reachable — a child only exists once awaited.\n        match &self.get(vm.heap).state {\n            GatherState::Awaited(_) => {}\n            GatherState::Completed(_) | GatherState::Failed(_) => {\n                value.drop_with(vm.heap);\n                return None;\n            }\n            GatherState::Pending => panic!(\"resolve_child called on a gather that was never awaited\"),\n        }\n\n        // Remove this child's slot-index mapping.\n        let indices: SmallVec<[usize; 1]> = self\n            .get_mut(vm.heap)\n            .as_awaited_mut()\n            .expect(\"resolve_child called on non-Awaited gather\")\n            .pending_children\n            .remove(&child_id)\n            .expect(\"resolve_child: child not registered with this gather\");\n\n        // Take `results` out so the writes can do their clones (which need\n        // `&Heap` access) without fighting the `&mut`-chain that\n        // `as_awaited_mut` requires. We put it back into the gather right\n        // after, before the completion scan.\n        let mut results = mem::take(\n            &mut self\n                .get_mut(vm.heap)","sourceCodeStart":1173,"sourceCodeEnd":1209,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/bytecode/vm/async_exec.rs#L1173-L1209","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["async","gather","internal-panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}