Hmbown/CodeWhale · error · anyhow::Error
Turn {turn_id} is no longer in progress and cannot be steere
Error message
Turn {turn_id} is no longer in progress and cannot be steered What it means
Inside the turn_mutation lock, steer_turn reloads the persisted TurnRecord and finds status != RuntimeTurnStatus::InProgress (crates/tui/src/runtime_threads.rs:6289). Disk state outranks the in-memory active_turn slot, so even though memory says the turn is active, the store says it ended; the steer is refused before any item is saved.
Source
Thrown at crates/tui/src/runtime_threads.rs:6289
bail!("Thread is not loaded");
};
let Some(active_turn) = active_thread.active_turn.as_ref() 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");
}
if !active_thread.engine.tx_op.same_channel(&engine.tx_op) {
bail!("Thread engine changed while preparing steer; retry");
}
let _turn_mutation = self.store.turn_mutation.lock();
let persistence = (|| -> Result<TurnRecord> {
let mut turn = self.store.load_turn(turn_id)?;
if turn.status != RuntimeTurnStatus::InProgress {
bail!("Turn {turn_id} is no longer in progress and cannot be steered");
}
self.store.save_item(&item)?;
turn.steer_count = turn.steer_count.saturating_add(1);
if !turn.item_ids.iter().any(|id| id == &item.id) {
turn.item_ids.push(item.id.clone());
}
self.store.save_turn(&turn)?;
Ok(turn)
})();
let turn = match persistence {
Ok(turn) => turn,
Err(error) => {
let cleanup = self.store.remove_item(&item.id);
return match cleanup {
Ok(()) => Err(error),
Err(cleanup_error) => Err(anyhow!(
"Failed to persist steer: {error}; cleanup also failed: {cleanup_error}"
)),View on GitHub (pinned to 0c42157ee5)
Solutions
- Treat this as turn-finished: re-check status and send a new message instead of steering
- Retry once only after confirming the turn is still InProgress in the store
- Avoid mutating runtime store records externally while turns run
- If it persists with a genuinely running turn, inspect for store write failures that flipped status
Example fix
// before
runtime.steer_turn(&thread_id, &turn_id, prompt).await?;
// after
let turn = store.load_turn(&turn_id)?;
if turn.status == RuntimeTurnStatus::InProgress {
runtime.steer_turn(&thread_id, &turn_id, prompt).await?;
} else {
runtime.send_message(&thread_id, prompt).await?;
} Defensive patterns
Strategy: validation
Validate before calling
// Read the persisted status right before steering.
let turn = store.load_turn(turn_id)?;
if turn.status != RuntimeTurnStatus::InProgress {
return runtime.send_message(thread_id, prompt).await; // turn already ended
}
runtime.steer_turn(thread_id, turn_id, prompt).await Type guard
fn turn_is_steerable(status: &RuntimeTurnStatus) -> bool {
matches!(status, RuntimeTurnStatus::InProgress)
} Try / catch
match runtime.steer_turn(thread_id, turn_id, prompt).await {
Ok(receipt) => { /* steered */ }
Err(err) if err.to_string().contains("no longer in progress") => {
// Disk says the turn ended: deliver as a new message instead.
runtime.send_message(thread_id, prompt).await?;
}
Err(err) => return Err(err),
} Prevention
- Prefer checking persisted turn status over in-memory state for steer decisions
- Never edit runtime store records out-of-band while turns run
- After crash recovery, reconcile all InProgress turns before allowing steers
- Fall back to send_message on any turn-ended error instead of surfacing it
When it happens
Trigger: The turn completes and is persisted between the in-memory check and load_turn; recovery logic marks the turn Interrupted/Failed while a steer is being prepared; external or manual edits to the turn record during a run.
Common situations: Fast-finishing turns racing a queued steer; crash-recovery sweeping InProgress turns to Failed while the UI steers; tooling that rewrites store records out-of-band.
Related errors
- ${name} is unavailable in Workflow scripts: runs must be det
- new Date()/Date() is unavailable in Workflow scripts: runs m
- lane `{}` was stopped before tmux dry-run start completed
- lane `{}` was stopped before tmux launch
- lane `{}` was stopped before inline start completed
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/4c90d5e1097f9e75.
Report an issue: GitHub.