{"record":{"id":"1508d18f3f40a3d3","repo":"Hmbown/CodeWhale","slug":"translation-credential-or-endpoint-changed-after-turn","errorCode":null,"errorMessage":"translation credential or endpoint changed after turn dispatch; refusing stale completion ownership","messagePattern":"translation credential or endpoint changed after turn dispatch; refusing stale completion ownership","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/ui/event_loop.rs","lineNumber":323,"sourceCode":"    )\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\n/// the default root (#5630). This id is only the initial store anchor; saved\n/// metadata retains the actual store binding when launch creates a new id.\npub(crate) fn ensure_runtime_session_id(app: &mut App) -> String {\n    if let Some(existing) = app\n        .current_session_id\n        .as_deref()\n        .map(str::trim)\n        .filter(|id| !id.is_empty())\n    {\n        return existing.to_string();","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/ui/event_loop.rs#L305-L341","documentation":"As a second, finer check after identity/model/endpoint match, `exact_translation_client` compares the client's `turn_route_receipt` for the provider identity against the receipt recorded at dispatch. A mismatch means the credential or endpoint details changed (e.g. API key rotated, endpoint credential updated) even though the identity is the same, so stale completion ownership is refused.","triggerScenarios":"Calling `exact_translation_client` when `route.receipt` is set but `validated.client.turn_route_receipt(&route.provider_identity)` returns a different value — credential rotation, re-auth, or endpoint configuration change after the turn was dispatched.","commonSituations":"API key refreshed in another window/process mid-turn; auth token expired and re-authenticated during a long turn; managed credential store rotated keys while the turn was in flight.","solutions":["Re-authenticate or re-enter credentials, then start a new turn so a fresh receipt is captured","Avoid rotating API keys or endpoint credentials while a turn is in progress","If this happens on every turn, check for something rewriting credentials (e.g. a config sync tool) each turn"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if let Some(receipt) = route.receipt.as_ref()\n    && &validated.client.turn_route_receipt(&route.provider_identity) != receipt {\n    eprintln!(\"credentials changed mid-turn; re-auth and start a new turn\");\n    return;\n}","typeGuard":"fn receipt_current(c: &ProviderClient, r: &TurnRoute) -> bool {\n    r.receipt.as_ref().map_or(true, |want| {\n        &c.turn_route_receipt(&r.provider_identity) == want\n    })\n}","tryCatchPattern":"match exact_translation_client(...).await {\n    Err(e) if e.to_string().contains(\"credential or endpoint changed\") => {\n        reauthenticate();\n        restart_turn();\n    }\n    r => r,\n}","preventionTips":["Schedule API-key rotation and endpoint changes between turns, never mid-turn","Disable credential-sync tools from rewriting keys during active sessions","Capture a fresh route receipt whenever credentials change instead of reusing the old one"],"tags":["rust","credentials","routing","auth"],"backgroundTag":"stale-credentials","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"}