Kuberwastaken/claurst · error

exchange_code: HTTP

Error message

exchange_code: HTTP {} — {}

What it means

The OAuth authorization-code token exchange POST returned a non-success HTTP status. The library surfaces the status code and the raw response body so the caller can see the token endpoint's error (e.g. invalid_grant) instead of an opaque failure.

Solutions

  1. Restart the full OAuth flow to get a fresh authorization code (codes are single-use and short-lived)
  2. Compare the client_id, client_secret, and redirect_uri sent to the token endpoint with the provider's registered values
  3. Read the body in the message for the provider's error code (e.g. invalid_grant, invalid_client) and fix accordingly
  4. If 5xx, retry later or check the provider's status page
Defensive patterns

Strategy: try-catch

Try / catch

match run_mcp_auth_session(server).await {
    Err(e) if e.to_string().starts_with("exchange_code: HTTP") => {
        // log body; if invalid_grant, restart the full authorize flow
    }
    other => other?,
}

Prevention

When it happens

Trigger: exchange_code (called from run_mcp_auth_session) POSTs the authorization code to the token endpoint and receives any HTTP status where !status.is_success().

Common situations: Authorization code expired or was already redeemed (refresh of the page double-submits); wrong client_id/client_secret; redirect_uri in the exchange doesn't match the one used in the authorize request; token endpoint URL misconfigured or provider outage (5xx).

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/73e6a7666b6baa28. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/oauth.rs:491

    let client = reqwest::Client::new();
    let params = [
        ("grant_type", "authorization_code"),
        ("code", code),
        ("code_verifier", verifier),
        ("redirect_uri", redirect_uri),
    ];

    let resp = client
        .post(token_endpoint)
        .form(&params)
        .send()
        .await
        .map_err(|e| anyhow::anyhow!("exchange_code: request failed: {}", e))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        anyhow::bail!("exchange_code: HTTP {} — {}", status, body);
    }

    #[derive(serde::Deserialize)]
    struct TokenResponse {
        access_token: String,
        refresh_token: Option<String>,
        expires_in: Option<u64>,
        scope: Option<String>,
    }

    let tr: TokenResponse = resp.json().await.map_err(|e| anyhow::anyhow!("exchange_code: bad JSON: {}", e))?;

    let expires_at = tr.expires_in.map(|secs| {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs()
            + secs

View on GitHub (pinned to b0637c97ec)