{"record":{"id":"feb5c731d21f41f7","repo":"Hmbown/CodeWhale","slug":"translation-route-changed-after-turn-dispatch-refusing-to","errorCode":null,"errorMessage":"translation route changed after turn dispatch; refusing to reuse a different provider client","messagePattern":"translation route changed after turn dispatch; refusing to reuse a different provider client","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/ui/event_loop.rs","lineNumber":313,"sourceCode":"    let identity = config\n        .resolve_persisted_provider_identity(\n            Some(route.provider.as_str()),\n            Some(&route.provider_identity),\n        )\n        .map_err(anyhow::Error::msg)?;\n    let validated = crate::route_runtime::resolve_runtime_route_for_identity(\n        config,\n        &identity,\n        Some(&route.model),\n    )\n    .map_err(anyhow::Error::msg)?\n    .validate()\n    .map_err(anyhow::Error::msg)?;\n    if validated.identity.key != route.provider_identity\n        || validated.model != route.model\n        || validated.candidate.endpoint().base_url != route.base_url\n    {\n        anyhow::bail!(\n            \"translation route changed after turn dispatch; refusing to reuse a different provider client\"\n        );\n    }\n    if let Some(receipt) = route.receipt.as_ref()\n        && &validated\n            .client\n            .turn_route_receipt(&route.provider_identity)\n            != receipt\n    {\n        anyhow::bail!(\n            \"translation credential or endpoint changed after turn dispatch; refusing stale completion ownership\"\n        );\n    }\n    Ok(Arc::new(validated.client))\n}\n\n/// Bind the Runtime thread store to a session before the process-owner lock\n/// is taken, so a second Codewhale on the same machine does not collide on","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/ui/event_loop.rs#L295-L331","documentation":"Before reusing a provider client for a translation request, `exact_translation_client` validates the freshly resolved route against the route recorded at turn dispatch time: provider identity key, model, and endpoint base_url must all match exactly. Any drift means the code refuses to send credentials/completions through a different client than the turn was started with.","triggerScenarios":"Calling `exact_translation_client` (from `run_event_loop`) when re-resolving the route yields a different `identity.key`, `model`, or `candidate.endpoint().base_url` than the `route` captured when the turn was dispatched — e.g. config/model profile changed mid-turn.","commonSituations":"User edits config or switches model profile while a turn is streaming; provider alias now resolves to another endpoint; persisted route cache updated between dispatch and completion.","solutions":["Let the current turn finish/fail, then start a new turn so the route is re-captured; do not change models/endpoints mid-turn","Revert the config/model change made during the turn and retry","If an alias changed unexpectedly, pin the provider/model explicitly instead of relying on auto resolution"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let validated = provider_client.validate().map_err(anyhow::Error::msg)?;\nif validated.identity.key != route.provider_identity\n    || validated.model != route.model\n    || validated.candidate.endpoint().base_url != route.base_url {\n    eprintln!(\"route changed; start a new turn instead of reusing the client\");\n    return;\n}","typeGuard":"fn route_matches(v: &Validated, r: &TurnRoute) -> bool {\n    v.identity.key == r.provider_identity\n        && v.model == r.model\n        && v.candidate.endpoint().base_url == r.base_url\n}","tryCatchPattern":"match exact_translation_client(...).await {\n    Err(e) if e.to_string().contains(\"route changed\") => {\n        abort_turn_and_prompt_restart();\n    }\n    r => r,\n}","preventionTips":["Don't switch models, profiles, or endpoints while a turn is in flight","Pin provider/model explicitly to avoid alias drift mid-turn","Treat route mismatch as turn-fatal: fail fast and start a fresh turn"],"tags":["rust","provider","routing","mid-turn-change"],"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"}