{"record":{"id":"eb030e844917aa2b","repo":"Hmbown/CodeWhale","slug":"turn-turn-id-moved-on-before-the-steer-reached-the-model","errorCode":null,"errorMessage":"Turn {turn_id} moved on before the steer reached the model; {STEER_DROPPED_REASON}","messagePattern":"Turn (.+?) moved on before the steer reached the model; (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10492,"sourceCode":"                }\n            };\n            // The reserved send has no await/failure point. From here the\n            // engine and durable record agree even if the API caller drops.\n            let outcome_rx = permit.send_with_outcome(prompt.clone());\n            touch_lru(&mut active.lru, thread_id);\n            queued_turn = turn.clone();\n            self.spawn_steer_settlement(turn, item, prompt, outcome_rx)\n        };\n\n        // The settler owns the verdict; this is only an observation of it.\n        // Steering a streaming turn settles in milliseconds, so the caller\n        // almost always gets the truth in-band. Behind a long tool call the\n        // engine will not look at its mailbox for minutes, and an API request\n        // must not hang that long — so the wait is bounded and the caller\n        // falls back to the honest `Queued` receipt it already has.\n        match tokio::time::timeout(STEER_SETTLE_WAIT, settle_rx).await {\n            Ok(Ok((SteerOutcome::Accepted, turn))) => Ok(turn),\n            Ok(Ok((SteerOutcome::Dropped, _))) => bail!(\n                \"Turn {turn_id} moved on before the steer reached the model; {STEER_DROPPED_REASON}\"\n            ),\n            Ok(Err(_)) => bail!(\"Steer settlement task ended before acknowledgement\"),\n            Err(_) => Ok(queued_turn),\n        }\n    }\n\n    pub async fn compact_thread(\n        &self,\n        thread_id: &str,\n        req: CompactThreadRequest,\n    ) -> Result<TurnRecord> {\n        // Compaction carries a concrete provider route just like a normal\n        // turn. Keep the same reload/admission boundary through durable engine\n        // handoff so it cannot dispatch an old credential or endpoint after a\n        // successful config reload.\n        let _config_admission = self.config_admission.read().await;\n        self.ensure_accepting_execution()?;","sourceCodeStart":10474,"sourceCodeEnd":10510,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10474-L10510","documentation":"Thrown when the engine explicitly reports `SteerOutcome::Dropped` on the settlement channel — the turn moved on (finished or advanced past the point where the steer could be injected) before the queued steer reached the model. The caller already returned a `Queued` receipt, so this error is the honest async correction that the steer was not delivered.","triggerScenarios":"Awaiting the steer settlement future (`settle_rx` with STEER_SETTLE_WAIT timeout) and receiving `(SteerOutcome::Dropped, _)` — happens when the turn completes or is interrupted between acceptance of the queued item and the engine's next mailbox check.","commonSituations":"Steer sent just as the model emits its final message; steer queued behind a turn that ends before the next API request; UI shows 'queued' then must reconcile with the drop.","solutions":["Fall back to starting a new turn containing the steering message so the user's input is never lost.","Handle the Dropped outcome in the caller by updating the UI receipt from Queued to Dropped and prompting the user to resend.","Steer earlier in the turn lifecycle: send steering input while the turn is clearly mid-stream rather than near its end."],"exampleFix":"// before\nlet turn = runtime.steer(thread_id, turn_id, item).await?;\n// after\nmatch runtime.steer(thread_id, turn_id, item).await {\n    Ok(turn) => turn,\n    Err(e) if e.to_string().contains(\"moved on before the steer\") => {\n        runtime.start_turn(thread_id, item).await? // deliver as a new turn\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"match runtime.steer(thread_id, turn_id, item).await {\n    Ok(turn) => Ok(turn),\n    Err(e) if e.to_string().contains(\"moved on before the steer\") => {\n        runtime.start_turn(thread_id, item).await // resend as a fresh turn\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Steer early in a turn, not near its expected end.","Always carry a fallback path from Queued steer receipts to a new-turn start.","Reconcile UI receipts: update Queued to Dropped when this error surfaces."],"tags":["tui","steer","dropped-message","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"}