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
- Read the logged response_summary/body to see the OAuth error code (invalid_grant, invalid_client, etc.)
- Restart the whole login flow to get a fresh authorization code instead of reusing it
- Verify client_id/client_secret and that redirect_uri matches the authorization request exactly
- Check PKCE code_verifier/state handling for corruption or mismatch between sessions
- 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
- Never reuse an authorization code — a fresh code per exchange
- Keep redirect_uri byte-identical between authorize and token requests
- Verify client credentials and PKCE verifier storage before exchanging
- Watch provider status pages; add retry only for 429/5xx
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
- Token 交换失败: status={}, body_len={}
- Token 交换失败 ({}),body_len={}
- Codex OAuth completed 命令失败: login_id={}, duration_ms={}, err
- OAuth 流程失败: {}
- Claude login start 响应缺少关键字段
AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05).
Data as JSON: /api/errors/f38f6545127f57e9.
Report an issue: GitHub.