{"record":{"id":"914e8fedea59c89e","repo":"pydantic/monty","slug":"gather-commit-frame-id-is-not-a-gatherfuture","errorCode":null,"errorMessage":"gather commit frame id is not a GatherFuture","messagePattern":"gather commit frame id is not a GatherFuture","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty/src/bytecode/vm/async_exec.rs","lineNumber":215,"sourceCode":"            awaiter,\n            parent_slot,\n            next: 0,\n            results: (0..item_count).map(|_| None).collect(),\n            pending_children: PendingChildren::new(),\n        }\n    }\n\n    /// Commits `frame`'s items left-to-right into result slots or pending\n    /// children, spawning coroutine children and installing awaiters on\n    /// external futures as it goes.\n    ///\n    /// Stops early at a `Pending` nested gather, returning the frame the caller\n    /// must commit before this one can continue; `Ok(None)` means every item is\n    /// committed. Any error leaves the items handled so far in `frame`, which\n    /// [`Self::unwind_gather_commits`] settles.\n    fn step_gather_commit(&mut self, frame: &mut GatherCommit) -> Result<Option<GatherCommit>, RunError> {\n        let HeapReadOutput::GatherFuture(gather) = self.heap.read(frame.gather) else {\n            panic!(\"gather commit frame id is not a GatherFuture\")\n        };\n        let gather_id = frame.gather;\n\n        while frame.next < frame.results.len() {\n            let idx = frame.next;\n            let item_id = gather.get(self.heap).items[idx];\n            if let Some(slots) = frame.pending_children.get_mut(&item_id) {\n                // Dedup: We've already registered this item in this commit pass —\n                // this is a duplicate item (e.g. `gather(coro, coro)`). Just\n                // append the new slot index to the existing entry.\n                slots.push(idx);\n                frame.next += 1;\n                continue;\n            }\n\n            let poll = match self.heap.read(item_id) {\n                HeapReadOutput::Coroutine(coro) => {\n                    // Reject reuse up-front: either the coroutine is no longer","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/bytecode/vm/async_exec.rs#L197-L233","documentation":"During async gather commit processing, step_gather_commit reads the heap entry identified by GatherCommit::gather and expects it to be a GatherFuture. If the heap read returns any other variant, this panic fires. It is an internal invariant: only code that just created a gather future should ever push a commit frame for it, so a mismatch means heap bookkeeping is corrupted.","triggerScenarios":"An internal bug where frame.gather points at a heap entry that was replaced, freed, or never a GatherFuture (e.g. refcount bug causing reuse, or wrong HeapId stored in the commit frame).","commonSituations":"Not user-triggerable in normal operation; surfaces during interpreter development, after changes to gather/await machinery, or under heap corruption from another bug (possibly exposed with memory-model-checks).","solutions":["Reproduce with a minimal async gather snippet and file a bug against monty with the code","Check for a refcount/heap bug: run with --features memory-model-checks to validate heap invariants","Inspect commit_gather_tree/open_gather_commit to confirm only fresh GatherFuture ids are stored in GatherCommit frames"],"exampleFix":"// before (interpreter bug)\nlet frame = GatherCommit { gather: wrong_id, .. };\n\n// after\nlet HeapReadOutput::GatherFuture(_) = self.heap.read(gather_id) else { panic!(...) };\nlet frame = GatherCommit { gather: gather_id, .. };","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// this is an interpreter-internal panic, not a catchable library error\n// isolate: run each async snippet in a fresh session so a corrupted heap cannot poison subsequent runs","preventionTips":["Report the reproducing snippet to the monty maintainers","Run --features memory-model-checks on async/gather changes","Keep HeapId bookkeeping changes out of GatherCommit construction"],"tags":["rust","async","gather","internal-invariant","interpreter"],"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"}