{"record":{"id":"988f6a8b1ce22ce6","repo":"Hmbown/CodeWhale","slug":"turn-turn-id-is-stopping-and-cannot-be-steered","errorCode":null,"errorMessage":"Turn {turn_id} is stopping and cannot be steered","messagePattern":"Turn (.+?) is stopping and cannot be steered","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10401,"sourceCode":"        let prompt = req.prompt.trim().to_string();\n        if prompt.is_empty() {\n            bail!(\"prompt is required\");\n        }\n\n        let engine = {\n            let mut active = self.active.lock().await;\n            let engine = {\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                if active_turn.interrupt_requested {\n                    bail!(\"Turn {turn_id} is stopping and cannot be steered\");\n                }\n                active_thread.engine.clone()\n            };\n            touch_lru(&mut active.lru, thread_id);\n            engine\n        };\n\n        let permit = engine\n            .reserve_steer()\n            .await\n            .map_err(|error| anyhow!(\"Failed to steer turn: {error}\"))?;\n\n        let now = Utc::now();\n        let queued_turn;\n        let item = TurnItemRecord {\n            schema_version: CURRENT_RUNTIME_SCHEMA_VERSION,\n            id: format!(\"item_{}\", &Uuid::new_v4().to_string()[..8]),\n            turn_id: turn_id.to_string(),","sourceCodeStart":10383,"sourceCodeEnd":10419,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10383-L10419","documentation":"steer_turn refuses to steer a turn that already has an interrupt requested (crates/tui/src/runtime_threads.rs:10400). Once interrupt_turn sets active_turn.interrupt_requested, the turn is winding down and queuing steering input would deliver a message to a turn the user asked to stop.","triggerScenarios":"Calling steer_turn after interrupt_turn(thread_id, turn_id) on the same turn but before the turn's terminal event arrives; concurrent interrupt + steer calls from different UI actions.","commonSituations":"User hits Escape (interrupt) then quickly types and sends a steering message; a batch script interleaving stop and steer commands; double-handling where a stop also triggers a queued 'please continue' steer.","solutions":["Do not steer after requesting an interrupt; wait for the turn's terminal event, then start a new turn with the text.","Track the interrupt_requested state (via turn.interrupt_requested events) and disable steering in the UI meanwhile.","Serialize user actions so a steer cannot be issued between interrupt and turn end."],"exampleFix":"// before\nmanager.interrupt_turn(&thread_id, &turn_id).await?;\nmanager.steer_turn(&thread_id, &turn_id, req).await?;\n// after\nmanager.interrupt_turn(&thread_id, &turn_id).await?;\nmanager.wait_turn_finished(&thread_id).await?;\nmanager.send_message(&thread_id, req.prompt).await?;","handlingStrategy":"validation","validationCode":"// never steer a turn that is being interrupted\nanyhow::ensure!(\n    !is_interrupt_requested(&thread_id),\n    \"turn is stopping; wait for it to finish before sending input\"\n);","typeGuard":"fn is_steerable(t: &ActiveTurn) -> bool { !t.interrupt_requested }","tryCatchPattern":"match manager.steer_turn(&tid, &turn_id, req.clone()).await {\n    Err(e) if e.to_string().contains(\"cannot be steered\") => {\n        manager.wait_turn_finished(&tid).await?;\n        manager.send_message(&tid, req.prompt).await?\n    }\n    other => other?,\n}","preventionTips":["After interrupt_turn, block steering until the turn's terminal event arrives","Serialize stop and steer actions in the UI (disable send while stopping)","Track interrupt_requested from turn.interrupt_requested events, not local assumptions"],"tags":["runtime","turn-lifecycle","interrupt","steer","race-condition"],"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"}