Hmbown/CodeWhale · error

Runtime turn interruption failed

Error message

Runtime turn interruption failed: {error}

What it means

During RuntimeThreadManager shutdown, each active turn is interrupted via interrupt_turn. If that fails AND the persisted turn is still InProgress, the manager records 'Runtime turn interruption failed' as the shutdown failure it eventually returns. It only counts as a failure when the turn genuinely never left the InProgress state, so a benign interrupt error that still left a terminal turn record is ignored.

Solutions

  1. Inspect the wrapped {error} from interrupt_turn to find the root cause (engine channel, engine not installed, etc.)
  2. Ensure turns reach a terminal status before shutdown: await or cancel the in-flight turn first
  3. Verify the turn store is writable and load_turn can read the turn record
  4. Retry shutdown after the engine worker has drained; the error is recorded only while the turn is stuck InProgress

Example fix

// before
let _ = manager.shutdown().await; // swallows nothing; error surfaces here
// after
for (thread_id, turn_id) in manager.active_turns().await {
    manager.interrupt_turn(&thread_id, &turn_id).await?;
}
manager.shutdown().await?;
Defensive patterns

Strategy: try-catch

Validate before calling

let turn = manager.store.load_turn(&turn_id)?;
if turn.status == RuntimeTurnStatus::InProgress { /* shutdown will interrupt; prepare for possible failure */ }

Type guard

fn turn_is_terminal(status: RuntimeTurnStatus) -> bool { status != RuntimeTurnStatus::InProgress }

Try / catch

match manager.shutdown().await {
    Err(e) if e.to_string().contains("interruption failed") => {
        // turn stuck InProgress: inspect interrupt_turn error inside {error}
        tracing::error!("turn interruption failed: {e:#}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling RuntimeThreadManager::shutdown while a turn is in flight and interrupt_turn (thread_id, turn_id) returns Err; additionally the store.load_turn check finds turn.status == RuntimeTurnStatus::InProgress, meaning the interrupt did not persist a terminal status.

Common situations: Shutting the runtime down while a model stream is still running; a wedged engine task that never responds to the interrupt; a storage error that prevents the turn record from being updated before shutdown.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/c43005d3191ef3cf. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_threads.rs:5199

                    .iter()
                    .filter_map(|(id, state)| {
                        state
                            .active_turn
                            .as_ref()
                            .map(|turn| (id.clone(), turn.turn_id.clone()))
                    })
                    .collect::<Vec<_>>(),
            )
        };
        let mut failure = None;
        for (thread_id, turn_id) in active_turns {
            if let Err(error) = self.interrupt_turn(&thread_id, &turn_id).await
                && !self
                    .store
                    .load_turn(&turn_id)
                    .is_ok_and(|turn| turn.status != RuntimeTurnStatus::InProgress)
            {
                failure = Some(anyhow!("Runtime turn interruption failed: {error}"));
            }
        }
        let workers = self.engine_workers.lock().clone();
        for engine in engines
            .iter()
            .chain(workers.iter().map(|(engine, _)| engine))
        {
            engine.cancel_with_reason(crate::core::engine::CancelReason::Internal);
        }
        for engine in engines
            .iter()
            .chain(workers.iter().map(|(engine, _)| engine))
        {
            let _ = engine.send(Op::Shutdown).await;
        }
        for (_, worker) in workers {
            if let Err(error) = await_retained_completion(&worker).await {
                failure = Some(anyhow!("Runtime engine shutdown failed: {error}"));

View on GitHub (pinned to 73e0f67d83)