{"record":{"id":"add6bf90bf5d616a","repo":"jlcodes99/cockpit-tools","slug":"token-status-body-len","errorCode":null,"errorMessage":"Token 交换失败: status={}, body_len={}","messagePattern":"Token 交换失败: status=(.+?), body_len=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cockpit-core/src/modules/codex_oauth.rs","lineNumber":893,"sourceCode":"\n    logger::log_info(\"Codex OAuth 开始交换 Token\");\n\n    // 官方 authorization-code exchange 使用 raw auth client，不附加运行时 originator headers。\n    let response = client\n        .post(TOKEN_ENDPOINT)\n        .form(&params)\n        .send()\n        .await\n        .map_err(|e| format!(\"Token 请求失败: {}\", e))?;\n\n    let status = response.status();\n    let body = response\n        .text()\n        .await\n        .map_err(|e| format!(\"读取响应失败: {}\", e))?;\n\n    if !status.is_success() {\n        logger::log_error(&format!(\n            \"Token 交换失败: status={}, body_len={}\",\n            status,\n            body.len()\n        ));\n        return Err(format!(\n            \"Token 交换失败: status={}, body_len={}\",\n            status,\n            body.len()\n        ));\n    }\n\n    logger::log_info(\"Codex OAuth Token 交换成功\");\n\n    let token_response: serde_json::Value =\n        serde_json::from_str(&body).map_err(|e| format!(\"解析 Token 响应失败: {}\", e))?;\n\n    let id_token = token_response\n        .get(\"id_token\")","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/crates/cockpit-core/src/modules/codex_oauth.rs#L875-L911","documentation":"In exchange_code_for_token_internal, after sending the OAuth authorization code to the provider's token endpoint, the HTTP response status is not a success (2xx). The error is logged with the status code and response body length, and a formatted error is returned to complete_oauth_login. The authorization code could not be exchanged for access/id/refresh tokens.","triggerScenarios":"Redeeming an expired or already-used authorization code; wrong redirect_uri / client_id in the token request; provider-side 4xx (invalid_grant, invalid_client) or 5xx outage; network proxy returning an error page.","commonSituations":"User took too long between authorize and exchange so the code expired; clock skew invalidating tokens; provider credentials rotated; OpenAI auth service temporarily down (5xx).","solutions":["Restart the OAuth login flow to obtain a fresh authorization code and retry the exchange","Verify redirect_uri and client parameters match exactly what was used in the authorize request","Check provider status/endpoint availability if status is 5xx, then retry","Inspect logs for the status code: 400/401 usually means invalid/expired code or misconfigured client"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// retry only on transient (5xx) failures; 4xx means the code/session must be redone\nfn is_retryable_token_status(status: u16) -> bool { status >= 500 }","typeGuard":null,"tryCatchPattern":"match start_oauth_login(app).await {\n    Err(e) if format!(\"{e}\").contains(\"Token 交换失败\") => {\n        if e.contains(\"status=5\") {\n            // provider outage: retry after a short backoff with a fresh code\n            tokio::time::sleep(Duration::from_secs(2)).await;\n            start_oauth_login(app).await\n        } else {\n            // 4xx: invalid/expired code or bad client config — restart flow, verify redirect_uri\n            Err(e)\n        }\n    }\n    other => other,\n}","preventionTips":["Exchange the authorization code immediately after redirect — codes expire in minutes","Ensure redirect_uri and client params match the authorize request exactly","Sync system clock (NTP) to avoid token validation skew","Never reuse an authorization code; start a fresh login on failure"],"tags":["oauth","codex","token-exchange","http"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}