Hmbown/CodeWhale · error
Turn is stopping and cannot be steered
Error message
Turn {turn_id} is stopping and cannot be steered What it means
steer_turn refuses to steer a turn that already has an interrupt requested (crates/tui/src/runtime_threads.rs:10400). Once interrupt_turn sets active_turn.interrupt_requested, the turn is winding down and queuing steering input would deliver a message to a turn the user asked to stop.
Solutions
- Do not steer after requesting an interrupt; wait for the turn's terminal event, then start a new turn with the text.
- Track the interrupt_requested state (via turn.interrupt_requested events) and disable steering in the UI meanwhile.
- Serialize user actions so a steer cannot be issued between interrupt and turn end.
Example fix
// before manager.interrupt_turn(&thread_id, &turn_id).await?; manager.steer_turn(&thread_id, &turn_id, req).await?; // after manager.interrupt_turn(&thread_id, &turn_id).await?; manager.wait_turn_finished(&thread_id).await?; manager.send_message(&thread_id, req.prompt).await?;
Defensive patterns
Strategy: validation
Validate before calling
// never steer a turn that is being interrupted
anyhow::ensure!(
!is_interrupt_requested(&thread_id),
"turn is stopping; wait for it to finish before sending input"
); Type guard
fn is_steerable(t: &ActiveTurn) -> bool { !t.interrupt_requested } Try / catch
match manager.steer_turn(&tid, &turn_id, req.clone()).await {
Err(e) if e.to_string().contains("cannot be steered") => {
manager.wait_turn_finished(&tid).await?;
manager.send_message(&tid, req.prompt).await?
}
other => other?,
} Prevention
- After interrupt_turn, block steering until the turn's terminal event arrives
- Serialize stop and steer actions in the UI (disable send while stopping)
- Track interrupt_requested from turn.interrupt_requested events, not local assumptions
When it happens
Trigger: Calling steer_turn after interrupt_turn(thread_id, turn_id) on the same turn but before the turn's terminal event arrives; concurrent interrupt + steer calls from different UI actions.
Common situations: User hits Escape (interrupt) then quickly types and sends a steering message; a batch script interleaving stop and steer commands; double-handling where a stop also triggers a queued 'please continue' steer.
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
- Turn is no longer in progress and cannot be steered
- No active turn on thread
- Thread engine changed while preparing steer; retry
- Turn is not active on thread
- Turn moved on before the steer reached the model;
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/988f6a8b1ce22ce6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:10401
let prompt = req.prompt.trim().to_string();
if prompt.is_empty() {
bail!("prompt is required");
}
let engine = {
let mut active = self.active.lock().await;
let engine = {
let Some(active_thread) = active.engines.get_mut(thread_id) else {
bail!("Thread is not loaded");
};
let Some(active_turn) = active_thread.active_turn.as_mut() else {
bail!("No active turn on thread {thread_id}");
};
if active_turn.turn_id != turn_id {
bail!("Turn {turn_id} is not active on thread {thread_id}");
}
if active_turn.interrupt_requested {
bail!("Turn {turn_id} is stopping and cannot be steered");
}
active_thread.engine.clone()
};
touch_lru(&mut active.lru, thread_id);
engine
};
let permit = engine
.reserve_steer()
.await
.map_err(|error| anyhow!("Failed to steer turn: {error}"))?;
let now = Utc::now();
let queued_turn;
let item = TurnItemRecord {
schema_version: CURRENT_RUNTIME_SCHEMA_VERSION,
id: format!("item_{}", &Uuid::new_v4().to_string()[..8]),
turn_id: turn_id.to_string(),View on GitHub (pinned to 73e0f67d83)