{"record":{"id":"5300fe2707806710","repo":"pydantic/monty","slug":"awaiter-gatherslot-gather-id-is-not-a-gatherfuture","errorCode":null,"errorMessage":"Awaiter::GatherSlot gather id is not a GatherFuture","messagePattern":"Awaiter::GatherSlot gather id is not a GatherFuture","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/monty/src/bytecode/vm/async_exec.rs","lineNumber":847,"sourceCode":"    ///   optionally switch VM context into `task_id` (calling\n    ///   `remove_from_ready_queue` first since `deliver_value_to_task`\n    ///   already queued it).\n    /// - `None` if the chain was consumed by an intermediate gather that's\n    ///   still in flight, or if the terminal task is gone (in which case\n    ///   the value is dropped).\n    fn deliver_awaiter_success(&mut self, mut awaiter: Awaiter, mut value: Value) -> Option<TaskId> {\n        let this = self;\n        loop {\n            match awaiter {\n                Awaiter::Task(t) => {\n                    this.deliver_value_to_task(t, value);\n                    return Some(t);\n                }\n                Awaiter::GatherSlot { gather, source } => {\n                    let gather_val = Value::Ref(gather);\n                    defer_drop!(gather_val, this);\n                    let HeapReadOutput::GatherFuture(mut outer) = this.heap.read(gather) else {\n                        panic!(\"Awaiter::GatherSlot gather id is not a GatherFuture\")\n                    };\n                    let success = outer.resolve_child(this, source, value)?;\n                    awaiter = success.awaiter;\n                    value = Value::Ref(success.list_id);\n                }\n            }\n        }\n    }\n\n    /// Walks the awaiter chain starting at `awaiter`, tearing each\n    /// intermediate gather down with `error`, and fails the terminal task.\n    ///\n    /// Returns:\n    /// - `Some(task_id)` if failure reached a live task — the caller may\n    ///   optionally switch VM context into it; the task's state is already\n    ///   `Failed(error)` so `resume_with_resolved_futures`'s post-loop check\n    ///   will raise the exception when control returns. (Callers that need\n    ///   the task in `Ready` instead — `handle_task_failure` — should","sourceCodeStart":829,"sourceCodeEnd":865,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/bytecode/vm/async_exec.rs#L829-L865","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hit during development on gather internals (resolve_child, Awaiter chaining) or heap lifetime changes.","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"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-14T16:17:12.679Z"}