{"record":{"id":"b02c377ea8b2a33d","repo":"Hmbown/CodeWhale","slug":"no-active-turn-on-thread-thread-id","errorCode":null,"errorMessage":"No active turn on thread {thread_id}","messagePattern":"No active turn on thread (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10342,"sourceCode":"        };\n\n        let turn = acceptance_rx\n            .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);","sourceCodeStart":10324,"sourceCodeEnd":10360,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10324-L10360","documentation":"RuntimeThreadManager::interrupt_turn (crates/tui/src/runtime_threads.rs:10335) looks up the thread's currently active turn under the active-state lock before issuing a cancel. When the thread is loaded but has no turn in flight (active_turn is None), it bails with this message. It means you called interrupt_turn for a thread that is idle.","triggerScenarios":"Calling interrupt_turn(thread_id, turn_id) when active_thread.active_turn is None: the turn already completed/failed, the turn never started, or the thread was just loaded from the store with no turn running.","commonSituations":"Double-clicking an interrupt key so the second interrupt races the first turn's completion; interrupting after a turn finished between listing turns and calling; automation scripts interrupting a thread whose turn already ended.","solutions":["Check the thread has an in-flight turn before calling interrupt_turn (track turn status from turn.* events).","Treat this error as a no-op: the desired end state (no running turn) is already true; catch and ignore.","Verify the thread_id is correct and that the turn you intend to interrupt was actually started on this thread."],"exampleFix":"// before\nmanager.interrupt_turn(&thread_id, &turn_id).await?;\n// after\nif let Err(e) = manager.interrupt_turn(&thread_id, &turn_id).await {\n    if !e.to_string().contains(\"No active turn\") {\n        return Err(e); // already-stopped turns are fine to ignore\n    }\n}","handlingStrategy":"try-catch","validationCode":"// track turn state from events; only call when a turn is live\nfn can_interrupt(thread: &ThreadState) -> bool { thread.active_turn_id.is_some() }","typeGuard":"fn has_active_turn(t: &ActiveThreadState) -> bool { t.active_turn.is_some() }","tryCatchPattern":"match manager.interrupt_turn(&tid, &turn_id).await {\n    Ok(r) => /* interrupted */,\n    Err(e) if e.to_string().contains(\"No active turn\") => { /* already stopped: no-op */ }\n    Err(e) => return Err(e),\n}","preventionTips":["Only offer interrupt in the UI while a turn is visibly running","Derive interrupt availability from turn lifecycle events, not cached turn IDs","Treat 'no active turn' interrupts as success — the goal state is already reached"],"tags":["runtime","turn-lifecycle","state-error","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"}