Hmbown/CodeWhale · error

translation credential or endpoint changed after turn…

Error message

translation credential or endpoint changed after turn dispatch; refusing stale completion ownership

What it means

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.

Solutions

  1. Re-authenticate or re-enter credentials, then start a new turn so a fresh receipt is captured
  2. Avoid rotating API keys or endpoint credentials while a turn is in progress
  3. If this happens on every turn, check for something rewriting credentials (e.g. a config sync tool) each turn
Defensive patterns

Strategy: validation

Validate before calling

if let Some(receipt) = route.receipt.as_ref()
    && &validated.client.turn_route_receipt(&route.provider_identity) != receipt {
    eprintln!("credentials changed mid-turn; re-auth and start a new turn");
    return;
}

Type guard

fn receipt_current(c: &ProviderClient, r: &TurnRoute) -> bool {
    r.receipt.as_ref().map_or(true, |want| {
        &c.turn_route_receipt(&r.provider_identity) == want
    })
}

Try / catch

match exact_translation_client(...).await {
    Err(e) if e.to_string().contains("credential or endpoint changed") => {
        reauthenticate();
        restart_turn();
    }
    r => r,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1508d18f3f40a3d3. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/ui/event_loop.rs:323

    )
    .map_err(anyhow::Error::msg)?
    .validate()
    .map_err(anyhow::Error::msg)?;
    if validated.identity.key != route.provider_identity
        || validated.model != route.model
        || validated.candidate.endpoint().base_url != route.base_url
    {
        anyhow::bail!(
            "translation route changed after turn dispatch; refusing to reuse a different provider client"
        );
    }
    if let Some(receipt) = route.receipt.as_ref()
        && &validated
            .client
            .turn_route_receipt(&route.provider_identity)
            != receipt
    {
        anyhow::bail!(
            "translation credential or endpoint changed after turn dispatch; refusing stale completion ownership"
        );
    }
    Ok(Arc::new(validated.client))
}

/// Bind the Runtime thread store to a session before the process-owner lock
/// is taken, so a second Codewhale on the same machine does not collide on
/// the default root (#5630). This id is only the initial store anchor; saved
/// metadata retains the actual store binding when launch creates a new id.
pub(crate) fn ensure_runtime_session_id(app: &mut App) -> String {
    if let Some(existing) = app
        .current_session_id
        .as_deref()
        .map(str::trim)
        .filter(|id| !id.is_empty())
    {
        return existing.to_string();

View on GitHub (pinned to 73e0f67d83)