zeroclaw-labs/zeroclaw · error · anyhow::Error
OpenAI OAuth token request failed ({status}): {body}
Error message
OpenAI OAuth token request failed ({status}): {body} What it means
The OpenAI token endpoint (auth.openai.com/oauth/token) returned a non-2xx response inside `parse_token_response`. This single chokepoint backs `exchange_code_for_tokens`, `refresh_access_token`, and device-code polling, so it fires for code exchange, token refresh, and device grants alike. The HTTP status and raw body are embedded; typical bodies are OAuth errors such as `invalid_grant` or `invalid_client`, or an HTML page from an intermediary.
Source
Thrown at crates/zeroclaw-providers/src/auth/openai_oauth.rs:396
None
}
pub fn extract_expiry_from_jwt(token: &str) -> Option<chrono::DateTime<Utc>> {
let payload = token.split('.').nth(1)?;
let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(payload)
.ok()?;
let claims: serde_json::Value = serde_json::from_slice(&decoded).ok()?;
let exp = claims.get("exp").and_then(|v| v.as_i64())?;
chrono::DateTime::<Utc>::from_timestamp(exp, 0)
}
async fn parse_token_response(response: reqwest::Response) -> Result<TokenSet> {
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
anyhow::bail!("OpenAI OAuth token request failed ({status}): {body}");
}
let token: TokenResponse = response
.json()
.await
.context("Failed to parse OpenAI token response")?;
let expires_at = token.expires_in.and_then(|seconds| {
if seconds <= 0 {
None
} else {
Some(Utc::now() + chrono::Duration::seconds(seconds))
}
});
Ok(TokenSet {
access_token: token.access_token,
refresh_token: token.refresh_token,View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the embedded status and body: `invalid_grant` on refresh means the refresh token is dead — run a fresh interactive login; on code exchange, restart the flow for a new code
- Use the same `PkceState` instance for `build_authorize_url` (challenge) and `exchange_code_for_tokens` (verifier)
- Confirm redirect_uri is exactly `http://localhost:1455/auth/callback` (OPENAI_OAUTH_REDIRECT_URI)
- If the body is HTML or status is 5xx/429, suspect the network path (proxy, outage) and retry with backoff
Example fix
// before: any failure aborts
let tokens = refresh_access_token(&client, &refresh).await?;
// after: invalid_grant triggers re-login, transient failures retry
let tokens = match refresh_access_token(&client, &refresh).await {
Ok(t) => t,
Err(e) if e.to_string().contains("invalid_grant") => run_interactive_login().await?,
Err(e) if e.to_string().contains("token request failed") => retry_with_backoff().await?,
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
// avoid needless refresh calls: only refresh when expiry is close
if let Some(exp) = extract_expiry_from_jwt(&tokens.access_token) {
if exp > Utc::now() + chrono::Duration::seconds(60) {
return Ok(tokens); // still valid, skip the token endpoint round-trip
}
} Type guard
fn is_openai_token_error(e: &anyhow::Error) -> bool {
e.to_string().contains("OpenAI OAuth token request failed")
}
fn is_invalid_grant(e: &anyhow::Error) -> bool {
e.to_string().contains("invalid_grant")
} Try / catch
let tokens = match refresh_access_token(&client, &refresh).await {
Ok(t) => t,
Err(e) if is_openai_token_error(&e) && is_invalid_grant(&e) => run_interactive_login().await?,
Err(e) if is_openai_token_error(&e) => retry_with_backoff(refresh_access_token(&client, &refresh)).await?,
Err(e) => return Err(e),
}; Prevention
- Exchange the authorization code exactly once, immediately after receiving it
- Persist the new refresh token after every refresh — rotation invalidates the old one
- Use one PkceState for URL construction and code exchange
When it happens
Trigger: Exchanging an authorization code that expired (codes are short-lived and single-use) or was already consumed; refreshing with a revoked or rotated refresh_token; a PKCE code_verifier that does not match the challenge sent in the authorize URL; any 4xx/5xx from the endpoint.
Common situations: App crashed after exchanging but before persisting tokens — retry replays the used code; refresh token invalidated by a login on another machine; corporate proxy intercepting TLS and answering with an error page; clock skew.
Related errors
- xAI OAuth token request failed ({status}): {}
- xAI OAuth token request failed ({status}): {body}
- Embedding API error {status}: {text}
- OAuth state mismatch
- Missing OAuth state in callback
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/950bff478138fd69.
Report an issue: GitHub.