Hmbown/CodeWhale · error · anyhow::Error
Thread engine changed while preparing steer; retry
Error message
Thread engine changed while preparing steer; retry
What it means
steer_turn captured an engine handle earlier, but under the active lock the registered engine's tx_op channel is no longer the same channel (crates/tui/src/runtime_threads.rs:6283). The thread's engine was swapped (reload after config/route change, recycle, unload/reload) while the steer was being prepared. The message explicitly says retry: this is a transient optimistic-concurrency failure, not corruption.
Source
Thrown at crates/tui/src/runtime_threads.rs:6283
started_at: Some(now),
ended_at: Some(now),
};
let receipt_rx = {
let mut active = self.active.lock().await;
let Some(active_thread) = active.engines.get(thread_id) else {
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) => {View on GitHub (pinned to 0c42157ee5)
Solutions
- Retry the steer after re-reading the thread/engine state (the error is designed to be retried)
- Serialize settings changes with in-flight turn operations per thread
- Keep steer paths short so the engine-generation window is small
- If persistent, check whether something is thrashing the engine (repeated reloads)
Example fix
// before
let receipt = runtime.steer_turn(&thread_id, &turn_id, prompt).await?;
// after
let receipt = loop {
match runtime.steer_turn(&thread_id, &turn_id, prompt).await {
Ok(r) => break r,
Err(e) if e.to_string().contains("retry") => continue, // engine swapped; retry
Err(e) => return Err(e),
}
}; Defensive patterns
Strategy: retry
Validate before calling
// Cheap pre-check: ensure the engine generation you hold is still current.
let engine = runtime.current_engine(thread_id).await;
if engine.tx_op.same_channel(&captured_engine.tx_op) {
runtime.steer_turn(thread_id, turn_id, prompt).await
} else {
retry_with_fresh_state(thread_id, turn_id, prompt).await
} Try / catch
let mut attempts = 0;
loop {
match runtime.steer_turn(thread_id, turn_id, prompt).await {
Ok(receipt) => break Ok(receipt),
Err(err) if attempts < 3 && err.to_string().contains("retry") => {
attempts += 1;
tokio::time::sleep(Duration::from_millis(50)).await;
}
Err(err) => break Err(err),
}
} Prevention
- Treat 'engine changed ... retry' as transient by contract: always have a bounded retry wrapper
- Apply model/route/settings changes only while the thread is idle
- Keep steer call paths short so the engine-swap window stays narrow
- Investigate engine thrash if retries routinely exceed one attempt
When it happens
Trigger: Changing model/route/thread settings while a steer is in flight; engine recycled after an error; LRU eviction and reload of the thread engine racing the steer; two clients steering through different engine generations.
Common situations: Settings UI applies a route change concurrently with a user steering a running turn; tests that mutate config mid-turn; long steer paths (awaiting reserve_steer permit) overlapping an engine swap.
Related errors
- Thread execution settings changed while preparing compaction
- permissions changed after they were listed; reload {} and re
- xAI OAuth lifecycle lock changed repeatedly while opening
- Fleet worker {worker_id} coordination state is busy; retry r
- Turn {turn_id} is no longer in progress and cannot be steere
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/a2193698104130d2.
Report an issue: GitHub.