Kuberwastaken/claurst · error

Failed to parse token response

Error message

Failed to parse token response: {}

What it means

The token endpoint returned HTTP 2xx but the body could not be deserialized as JSON. The code calls resp.json::<serde_json::Value>() and maps the serde error into this anyhow error. It indicates the successful response was not valid JSON, which is unexpected for a well-behaved OAuth server.

Solutions

  1. Dump the raw response body and inspect it; look for HTML from a proxy or captive portal.
  2. Bypass or properly configure the corporate proxy for this host.
  3. Verify CODEX_TOKEN_URL is the correct OAuth token endpoint and was not changed.
  4. Retry later if the provider is having an incident.
Defensive patterns

Strategy: try-catch

Validate before calling

// capture raw body first so failures are debuggable
let raw = resp.text().await?;
let body: serde_json::Value = serde_json::from_str(&raw)
    .map_err(|e| anyhow!("Failed to parse token response: {} (body: {})", e, &raw[..raw.len().min(200)]))?;

Try / catch

// catch serde errors and include the offending body
match serde_json::from_str::<serde_json::Value>(&raw) {
    Ok(v) => Ok(v),
    Err(e) => Err(anyhow!("non-JSON token response: {}", e)),
}

Prevention

When it happens

Trigger: exchange_code_for_tokens receives a 200 response whose body is HTML (proxy/login portal interstitial), empty, truncated, or otherwise non-JSON, so resp.json().await fails.

Common situations: Corporate proxy rewriting responses with an HTML error/notice page; misconfigured CODEX_TOKEN_URL pointing at an HTML page; captive portal Wi-Fi intercepting HTTPS; server returning a 200 with empty body during partial outage.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src-rust/crates/cli/src/codex_oauth_flow.rs:211

    ];

    let resp = client
        .post(CODEX_TOKEN_URL)
        .form(&params)
        .send()
        .await
        .map_err(|e| anyhow!("Failed to exchange code: {}", e))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        bail!("Token exchange failed ({}): {}", status, body);
    }

    let body: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| anyhow!("Failed to parse token response: {}", e))?;

    let access_token = body["access_token"]
        .as_str()
        .unwrap_or("")
        .to_string();

    if access_token.is_empty() {
        bail!("No access_token in response");
    }

    let refresh_token = body["refresh_token"].as_str().map(|s| s.to_string());
    let account_id = extract_account_id_from_jwt(&access_token);

    Ok(CodexTokens {
        access_token,
        refresh_token,
        account_id,
        expires_at: None,

View on GitHub (pinned to b0637c97ec)