jlcodes99/cockpit-tools · error

Token 交换失败: status={}, body_len={}

Error message

Token 交换失败: status={}, body_len={}

What it means

exchange_code_for_token_internal posts the authorization code to Codex's token endpoint; when the HTTP response status is not a success, it logs and returns 'Token 交换失败: status={}, body_len={}'. This is a server-side rejection of the code exchange (invalid/expired code, bad client credentials, wrong redirect_uri) and the response body is summarized for debugging.

Source

Thrown at src-tauri/src/modules/codex_oauth.rs:1078

    let status = response.status();
    let body = response
        .text()
        .await
        .map_err(|e| format!("读取响应失败: {}", e))?;

    if !status.is_success() {
        let response_summary = serde_json::from_str::<serde_json::Value>(&body)
            .map(|value| crate::modules::codex_auth_diagnostic::oauth_response_summary(&value))
            .unwrap_or_else(|_| serde_json::json!({"body_type":"non_json"}));
        crate::modules::codex_auth_diagnostic::log_event(
            "oauth_token_exchange_failed",
            serde_json::json!({
                "status": status.as_u16(),
                "body_length": body.len(),
                "response": response_summary,
            }),
        );
        logger::log_error(&format!(
            "Token 交换失败: status={}, body_len={}",
            status,
            body.len()
        ));
        return Err(format!(
            "Token 交换失败: status={}, body_len={}",
            status,
            body.len()
        ));
    }

    logger::log_info("Codex OAuth Token 交换成功");

    let token_response: serde_json::Value =
        serde_json::from_str(&body).map_err(|e| format!("解析 Token 响应失败: {}", e))?;

    crate::modules::codex_auth_diagnostic::log_event(
        "oauth_token_exchange_response",

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Read the logged response_summary/body to see the OAuth error code (invalid_grant, invalid_client, etc.)
  2. Restart the whole login flow to get a fresh authorization code instead of reusing it
  3. Verify client_id/client_secret and that redirect_uri matches the authorization request exactly
  4. Check PKCE code_verifier/state handling for corruption or mismatch between sessions
  5. Check Codex service status if status is 5xx, then retry

Example fix

// before
return Err(format!("Token 交换失败: status={}, body_len={}", status, body.len()));
// after
return Err(format!("Token 交换失败: status={}, detail={}", status,
    truncate_log_text(&response_summary, 256)));
Defensive patterns

Strategy: try-catch

Validate before calling

// Before exchanging, validate prerequisites
fn can_exchange(code_age_secs: u64, code_used: bool) -> Result<(), &'static str> {
    if code_used { return Err("authorization code already consumed"); }
    if code_age_secs > 300 { return Err("authorization code likely expired"); }
    Ok(())
}

Try / catch

match complete_oauth_login(args).await {
    Err(e) if e.contains("Token 交换失败") => {
        // parse status from message; 4xx => restart full OAuth flow, 5xx => retry later
        if e.contains("status=5") { schedule_retry(); } else { restart_oauth_flow(); }
    }
    Ok(t) => store_tokens(t),
    Err(e) => eprintln!("{}", e),
}

Prevention

When it happens

Trigger: Calling complete_oauth_login after the callback when the token endpoint returns 400/401/403/5xx: the code was already used or expired, PKCE verifier mismatch, client_id/client_secret wrong, redirect_uri not identical to the one used in the authorization request, or Codex token service outage.

Common situations: Retrying a login whose code was already exchanged (code is single-use); clock skew invalidating PKCE/claims; environment misconfiguration pointing at a staging token endpoint; provider rotating client secrets; user taking longer than the code's ~1-10 min validity.

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/f38f6545127f57e9. Report an issue: GitHub.