Hmbown/CodeWhale · error

Turn is not active on thread

Error message

Turn {turn_id} is not active on thread {thread_id}

What it means

In interrupt_turn (crates/tui/src/runtime_threads.rs:10344), after finding an active turn, the manager compares its turn_id to the requested one. If they differ, it bails: each thread runs at most one turn, and you asked to interrupt a turn that is not the one currently executing on that thread.

Solutions

  1. Fetch the thread's current active turn_id (from turn lifecycle events) and pass that instead of the cached one.
  2. Re-read the turn record from the store to confirm the turn is still in progress before interrupting.
  3. If the stale turn already finished, treat the interrupt as done and skip the call.

Example fix

// before
let turn_id = last_seen_turn_id.clone();
manager.interrupt_turn(&thread_id, &turn_id).await?;
// after
let turn_id = current_active_turn_id(&thread_id).ok_or_else(|| anyhow!("no active turn"))?;
manager.interrupt_turn(&thread_id, &turn_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

// confirm the turn you hold is the one in flight
anyhow::ensure!(
    current_active_turn_id(&thread_id) == Some(turn_id.to_string()),
    "turn {} is not the active turn",
    turn_id
);

Type guard

fn is_current_turn(active: &ActiveTurn, turn_id: &str) -> bool { active.turn_id == turn_id }

Try / catch

if let Err(e) = manager.interrupt_turn(&tid, &turn_id).await {
    if e.to_string().contains("is not active") { refresh_turn_id(&tid).await?; }
    else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling interrupt_turn(thread_id, turn_id) with a stale or mismatched turn_id while a different turn_id is active on the thread (e.g. the old turn ended and a new one started).

Common situations: Caching turn IDs across turns; interrupting from a UI row bound to an older turn after the user already started a follow-up turn; concurrent automations each tracking their own turn IDs.

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/e387c37b253defd2. Report an issue: GitHub.

Appendix: source

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

            .await
            .map_err(|_| anyhow!("Turn lifecycle task ended before acknowledgement"))?
            .map_err(anyhow::Error::msg)?;
        Ok((turn, false))
        })
        .await
    }

    pub async fn interrupt_turn(&self, thread_id: &str, turn_id: &str) -> Result<TurnRecord> {
        {
            let mut active = self.active.lock().await;
            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}");
            }
            active_turn.interrupt_requested = true;
            // Wake the monitor's approval wait so it can consume the Engine's
            // terminal receipt. Revoke only this turn's minted capabilities.
            // Registration takes the same active lock, so a late event cannot
            // install a new waiter after this sweep.
            self.pending_approvals.lock().retain(|_, entry| {
                entry.thread_id != thread_id || entry.request.turn_id != turn_id
            });
            if let Some(compaction_id) = active_turn.compaction_id.as_deref() {
                active_thread.engine.cancel_compaction(compaction_id)?;
            } else {
                active_thread.engine.cancel();
            }
            touch_lru(&mut active.lru, thread_id);
        }

        self.emit_event(

View on GitHub (pinned to 73e0f67d83)