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
- Restart the full OAuth flow to get a fresh authorization code (codes are single-use and short-lived)
- Compare the client_id, client_secret, and redirect_uri sent to the token endpoint with the provider's registered values
- Read the body in the message for the provider's error code (e.g. invalid_grant, invalid_client) and fix accordingly
- 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
- Never reuse or refresh authorization codes — they are single-use
- Keep client_id/secret/redirect_uri consistent between authorize and token requests
- Exchange the code immediately after redirect; codes expire within minutes
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
- Token exchange failed
- Token exchange failed
- Bridge register: server returned
- API key creation failed
- refresh: HTTP
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(¶ms)
.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()
+ secsView on GitHub (pinned to b0637c97ec)