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

  1. Treat this as turn-finished: re-check status and send a new message instead of steering
  2. Retry once only after confirming the turn is still InProgress in the store
  3. Avoid mutating runtime store records externally while turns run
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/4c90d5e1097f9e75. Report an issue: GitHub.