xai-org/grok-build · error

Token exchange error: {detail}

Error message

Token exchange error: {detail}

What it means

Catch-all branch: the token endpoint returned an unrecognized OAuth2 error code during device-code exchange. The client logs the raw error and detail and aborts with the server's description so the developer can diagnose the actual server-side cause.

Source

Thrown at crates/codegen/xai-grok-shell/src/auth/device_code.rs:278

            "slow_down" => {
                poll_interval += std::time::Duration::from_secs(DEVICE_SLOW_DOWN_INCREMENT_SECS);
                continue;
            }
            "access_denied" => {
                tracing::warn!(description = detail, "device auth authorization denied");
                anyhow::bail!("Authorization denied. The user rejected the request.");
            }
            "expired_token" => {
                tracing::warn!(description = detail, "device auth token expired");
                anyhow::bail!("Device code expired. Run `grok login --device-auth` again.");
            }
            other => {
                tracing::warn!(
                    error = other,
                    description = detail,
                    "device auth token exchange failed"
                );
                anyhow::bail!("Token exchange error: {detail}");
            }
        }
    }
}

/// Device-code login shared by the TUI and CLI.
///
/// With `channels` (TUI) the verification URL goes to `url_tx` and the browser
/// opens automatically; on failure the copyable URL is the fallback. Without
/// `channels` (CLI) the URL + code are printed to stderr via `prompt_and_poll`.
/// `code_rx` is unused here. The caller reports success (`✓ Signed in`).
///
/// Takes `channels` by `&mut`, consuming it only after the device code is
/// obtained, so callers can reuse it for a loopback fallback on `NotEnabled`.
pub(crate) async fn run_device_code_login_channels(
    issuer: &str,
    client_id: &str,
    scopes: &[String],

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Read the `detail` in the message — it contains the server's error description.
  2. Verify the client_id/OAuth2 configuration (grok_com_config.oauth2) matches the issuer.
  3. Check the authorization server's status/logs for outages or gateway errors.
  4. Retry once with a fresh device code; if persistent, update the client or report the unhandled error code.

Example fix

// server response example causing it
{"error": "invalid_grant", "error_description": "device_code already used"}
// fix: do not reuse a device code; start a new login
grok login --device-auth
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: confirm the token endpoint is reachable and returns JSON
curl -sS -o /dev/null -w '%{http_code}' https://x.ai/oauth/token

Try / catch

match complete_device_code_login(&client, &pending).await {
    Err(e) if e.to_string().starts_with("Token exchange error") => {
        eprintln!("Server said: {e}. Check client_id config and issuer status, then retry.");
    }
    other => other?,
}

Prevention

When it happens

Trigger: complete_device_code_login gets a token response whose error field is not one of authorization_pending, slow_down, access_denied, or expired_token — e.g. invalid_grant, invalid_client, or server_error.

Common situations: Client credentials/client_id mismatch after a config change; reverse proxy or gateway injecting error responses (502/503 HTML parsed as error); server API version drift introducing new error codes; device_code already consumed.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/6bbee145922c340c. Report an issue: GitHub.