{"record":{"id":"2aa3069f79eae452","repo":"Hmbown/CodeWhale","slug":"runtime-is-shutting-down-recovery-receipts-remain-pending","errorCode":null,"errorMessage":"Runtime is shutting down; recovery receipts remain pending","messagePattern":"Runtime is shutting down; recovery receipts remain pending","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":6907,"sourceCode":"        }\n        self.append_and_broadcast_event(\n            &params.thread_id,\n            Some(&params.turn_id),\n            None,\n            \"tool_call.canceled\",\n            payload,\n        )\n        .await?;\n        Ok(true)\n    }\n\n    async fn flush_recovery_receipts_for_thread(&self, thread_id: &str) -> Result<()> {\n        if !self.recovery_receipts.lock().contains_key(thread_id) {\n            return Ok(());\n        }\n        let _recovery_flush = self.recovery_flush.lock().await;\n        if self.cancel_token.is_cancelled() {\n            bail!(\"Runtime is shutting down; recovery receipts remain pending\");\n        }\n        loop {\n            let next = self\n                .recovery_receipts\n                .lock()\n                .get(thread_id)\n                .and_then(|receipts| receipts.first())\n                .cloned();\n            let Some(receipt) = next else {\n                return Ok(());\n            };\n\n            // An in-process monitor failure may leave retry-safe calls in the\n            // live registry. Retry their supervised cancellation before the\n            // static restart-recovery receipts below. Startup recovery has no\n            // live registry entries, so this is a no-op in that case.\n            self.settle_dynamic_tools_for_terminal_turn(thread_id, &receipt.turn.id)\n                .await?;","sourceCodeStart":6889,"sourceCodeEnd":6925,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L6889-L6925","documentation":"`flush_recovery_receipts_for_thread` drains pending recovery receipts for a thread after acquiring the recovery flush lock. If the runtime's cancel token has fired (shutdown in progress), the flush aborts with this error rather than half-publishing receipts. Pending receipts are intentionally left in `recovery_receipts` so a future run can replay them.","triggerScenarios":"Calling flush_recovery_receipts_for_thread (or a turn-completion path that invokes it) while `cancel_token.is_cancelled()` is true — i.e., during runtime shutdown/Ctrl-C.","commonSituations":"User interrupts the TUI while a turn with recovery receipts is finishing; shutdown races an in-flight flush; tests tearing down the runtime while receipts are pending.","solutions":["Do not treat this as data loss: receipts remain pending and will be flushed on the next run.","Check `cancel_token.is_cancelled()` before initiating a flush and skip gracefully.","Await shutdown completion before starting new turn-completion work that triggers flushing.","If a receipt must not be lost, persist it to the store before shutdown instead of relying on in-memory flush."],"exampleFix":"// before\nruntime.flush_recovery_receipts_for_thread(thread_id).await?;\n// after\nif !runtime.is_shutting_down() {\n    runtime.flush_recovery_receipts_for_thread(thread_id).await?;\n} else {\n    eprintln!(\"shutdown in progress; receipts deferred\");\n}","handlingStrategy":"try-catch","validationCode":"if cancel_token.is_cancelled() {\n    eprintln!(\"shutdown in progress; deferring recovery receipt flush\");\n    return Ok(());\n}","typeGuard":null,"tryCatchPattern":"match runtime.flush_recovery_receipts_for_thread(thread_id).await {\n    Err(e) if e.to_string().contains(\"shutting down\") => {\n        info!(\"receipts for {} left pending; will flush next run\", thread_id);\n    }\n    other => other?,\n}","preventionTips":["Check the cancel token before starting turn-completion work during shutdown.","Persist recovery receipts durably so a deferred flush is always safe.","Let shutdown complete before triggering flushes from tests or cleanup hooks."],"tags":["shutdown","recovery","cancellation"],"backgroundTag":"operation-cancelled-during-shutdown","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}