{"record":{"id":"e387c37b253defd2","repo":"Hmbown/CodeWhale","slug":"turn-turn-id-is-not-active-on-thread-thread-id","errorCode":null,"errorMessage":"Turn {turn_id} is not active on thread {thread_id}","messagePattern":"Turn (.+?) is not active on thread (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10345,"sourceCode":"            .await\n            .map_err(|_| anyhow!(\"Turn lifecycle task ended before acknowledgement\"))?\n            .map_err(anyhow::Error::msg)?;\n        Ok((turn, false))\n        })\n        .await\n    }\n\n    pub async fn interrupt_turn(&self, thread_id: &str, turn_id: &str) -> Result<TurnRecord> {\n        {\n            let mut active = self.active.lock().await;\n            let Some(active_thread) = active.engines.get_mut(thread_id) else {\n                bail!(\"Thread is not loaded\");\n            };\n            let Some(active_turn) = active_thread.active_turn.as_mut() else {\n                bail!(\"No active turn on thread {thread_id}\");\n            };\n            if active_turn.turn_id != turn_id {\n                bail!(\"Turn {turn_id} is not active on thread {thread_id}\");\n            }\n            active_turn.interrupt_requested = true;\n            // Wake the monitor's approval wait so it can consume the Engine's\n            // terminal receipt. Revoke only this turn's minted capabilities.\n            // Registration takes the same active lock, so a late event cannot\n            // install a new waiter after this sweep.\n            self.pending_approvals.lock().retain(|_, entry| {\n                entry.thread_id != thread_id || entry.request.turn_id != turn_id\n            });\n            if let Some(compaction_id) = active_turn.compaction_id.as_deref() {\n                active_thread.engine.cancel_compaction(compaction_id)?;\n            } else {\n                active_thread.engine.cancel();\n            }\n            touch_lru(&mut active.lru, thread_id);\n        }\n\n        self.emit_event(","sourceCodeStart":10327,"sourceCodeEnd":10363,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10327-L10363","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Fetch the thread's current active turn_id (from turn lifecycle events) and pass that instead of the cached one.","Re-read the turn record from the store to confirm the turn is still in progress before interrupting.","If the stale turn already finished, treat the interrupt as done and skip the call."],"exampleFix":"// before\nlet turn_id = last_seen_turn_id.clone();\nmanager.interrupt_turn(&thread_id, &turn_id).await?;\n// after\nlet turn_id = current_active_turn_id(&thread_id).ok_or_else(|| anyhow!(\"no active turn\"))?;\nmanager.interrupt_turn(&thread_id, &turn_id).await?;","handlingStrategy":"validation","validationCode":"// confirm the turn you hold is the one in flight\nanyhow::ensure!(\n    current_active_turn_id(&thread_id) == Some(turn_id.to_string()),\n    \"turn {} is not the active turn\",\n    turn_id\n);","typeGuard":"fn is_current_turn(active: &ActiveTurn, turn_id: &str) -> bool { active.turn_id == turn_id }","tryCatchPattern":"if let Err(e) = manager.interrupt_turn(&tid, &turn_id).await {\n    if e.to_string().contains(\"is not active\") { refresh_turn_id(&tid).await?; }\n    else { return Err(e); }\n}","preventionTips":["Keep one authoritative 'current turn id' per thread updated from turn events","Never cache turn_id across automatic retries or follow-up turns","Read the turn record from the store before interrupting to confirm InProgress status"],"tags":["runtime","turn-lifecycle","stale-identifier","tui"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}