Hmbown/CodeWhale · warning
Runtime is shutting down; recovery receipts remain pending
Error message
Runtime is shutting down; recovery receipts remain pending
What it means
`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.
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.
Example fix
// before
runtime.flush_recovery_receipts_for_thread(thread_id).await?;
// after
if !runtime.is_shutting_down() {
runtime.flush_recovery_receipts_for_thread(thread_id).await?;
} else {
eprintln!("shutdown in progress; receipts deferred");
} Defensive patterns
Strategy: try-catch
Validate before calling
if cancel_token.is_cancelled() {
eprintln!("shutdown in progress; deferring recovery receipt flush");
return Ok(());
} Try / catch
match runtime.flush_recovery_receipts_for_thread(thread_id).await {
Err(e) if e.to_string().contains("shutting down") => {
info!("receipts for {} left pending; will flush next run", thread_id);
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- MCP connection ' ' was cancelled
- background hook supervisor is unavailable
- cancelled tool result is always model-visible
- engine event channel closed before turn
- Failed to start turn: engine operation channel closed
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/2aa3069f79eae452.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:6907
}
self.append_and_broadcast_event(
¶ms.thread_id,
Some(¶ms.turn_id),
None,
"tool_call.canceled",
payload,
)
.await?;
Ok(true)
}
async fn flush_recovery_receipts_for_thread(&self, thread_id: &str) -> Result<()> {
if !self.recovery_receipts.lock().contains_key(thread_id) {
return Ok(());
}
let _recovery_flush = self.recovery_flush.lock().await;
if self.cancel_token.is_cancelled() {
bail!("Runtime is shutting down; recovery receipts remain pending");
}
loop {
let next = self
.recovery_receipts
.lock()
.get(thread_id)
.and_then(|receipts| receipts.first())
.cloned();
let Some(receipt) = next else {
return Ok(());
};
// An in-process monitor failure may leave retry-safe calls in the
// live registry. Retry their supervised cancellation before the
// static restart-recovery receipts below. Startup recovery has no
// live registry entries, so this is a no-op in that case.
self.settle_dynamic_tools_for_terminal_turn(thread_id, &receipt.turn.id)
.await?;View on GitHub (pinned to 73e0f67d83)