{"record":{"id":"8a172853cd1c899f","repo":"pydantic/monty","slug":"pending-externals-entry-doesn-t-point-to-an-externalfuture","errorCode":null,"errorMessage":"pending_externals entry doesn't point to an ExternalFuture","messagePattern":"pending_externals entry doesn't point to an ExternalFuture","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/monty/src/bytecode/vm/async_exec.rs","lineNumber":780,"sourceCode":"    /// `Resolved(value)`, and delivers `value` to the awaiter (if any).\n    pub fn resolve_future(&mut self, call_id: u32, value: Value) {\n        let call_id = CallId::new(call_id);\n\n        let Some(future_id) = self.scheduler.take_pending_external(call_id) else {\n            value.drop_with(self);\n            return;\n        };\n\n        // Ensure future cleaned up on all paths\n        let fut_val = Value::Ref(future_id);\n        let this = self;\n        defer_drop!(fut_val, this);\n\n        let mut value_guard = DropGuard::new(value, this);\n        let (value, this) = value_guard.as_parts_mut();\n\n        let HeapReadOutput::ExternalFuture(mut fut) = this.heap.read(future_id) else {\n            panic!(\"pending_externals entry doesn't point to an ExternalFuture\")\n        };\n\n        let awaiter_and_value = match &mut fut.get_mut(this.heap).state {\n            ExternalFutureState::Pending { awaiter } => awaiter.take().map(|a| (a, value.clone_with_heap(this.heap))),\n            ExternalFutureState::Resolved(_) | ExternalFutureState::Failed(_) => {\n                panic!(\"resolve_future: future was already resolved\")\n            }\n        };\n\n        let (value, this) = value_guard.into_parts();\n        fut.get_mut(this.heap).state = ExternalFutureState::Resolved(value);\n\n        if let Some((awaiter, value)) = awaiter_and_value {\n            this.deliver_awaiter_success(awaiter, value);\n        }\n    }\n\n    /// Pushes `value` onto `task_id`'s stack and marks it ready. If the task","sourceCodeStart":762,"sourceCodeEnd":798,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty/src/bytecode/vm/async_exec.rs#L762-L798","documentation":"resolve_future looks up the future id from pending_externals and expects an ExternalFuture heap entry; a different type panics. pending_externals should only ever contain ids of ExternalFutures, so this indicates bookkeeping corruption or slot reuse. Not triggerable from user code.","triggerScenarios":"The host resolves a pending external future (resume_with_resolved_futures → resolve_future) and the stored id resolves to a non-ExternalFuture heap entry — stale id after entry freed and slot reused.","commonSituations":"Encountered while modifying external-future resolution or heap reuse in async_exec.rs; possibly after changing how pending_externals entries are removed.","solutions":["Ensure pending_externals entries are removed exactly when the future is resolved/dropped, not earlier","Check no code dec_refs an ExternalFuture while its id remains in pending_externals","Assert the heap type when inserting into pending_externals and on removal","Report with a repro script if hit without local changes"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat pending_externals as owning its ids until resolution completes","Remove entries exactly once, at resolution time","Type-assert on insertion into pending_externals"],"tags":["async","internal-panic","futures"],"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"}