{"record":{"id":"6c17a2f5ff97f57e","repo":"jlcodes99/cockpit-tools","slug":"codex-oauth-completed-login-id-duration","errorCode":null,"errorMessage":"Codex OAuth completed 命令失败: login_id={}, duration_ms={}, error={}","messagePattern":"Codex OAuth completed 命令失败: login_id=(.+?), duration_ms=(.+?), error=(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/commands/codex_account_commands.rs","lineNumber":1949,"sourceCode":") -> Result<(), String> {\n    codex_oauth::open_incognito_oauth_window(&app_handle, &auth_url)\n}\n\n/// OAuth：浏览器授权完成后按 loginId 完成登录\n#[tauri::command]\npub async fn codex_oauth_login_completed(\n    login_id: String,\n    reauth_account_id: Option<String>,\n) -> Result<CodexAccount, String> {\n    let started_at_ms = chrono::Utc::now().timestamp_millis();\n    logger::log_info(&format!(\n        \"Codex OAuth completed 命令开始: login_id={}, started_at_ms={}\",\n        login_id, started_at_ms\n    ));\n    let tokens = match codex_oauth::complete_oauth_login(&login_id).await {\n        Ok(tokens) => tokens,\n        Err(e) => {\n            logger::log_error(&format!(\n                \"Codex OAuth completed 命令失败: login_id={}, duration_ms={}, error={}\",\n                login_id,\n                chrono::Utc::now().timestamp_millis() - started_at_ms,\n                e\n            ));\n            return Err(e);\n        }\n    };\n    let account = save_codex_oauth_tokens(tokens, reauth_account_id.as_deref()).await?;\n    logger::log_info(&format!(\n        \"Codex OAuth completed 命令成功: login_id={}, duration_ms={}, account_id={}, account_email={}\",\n        login_id,\n        chrono::Utc::now().timestamp_millis() - started_at_ms,\n        account.id,\n        account.email\n    ));\n    Ok(account)\n}","sourceCodeStart":1931,"sourceCodeEnd":1967,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src-tauri/src/commands/codex_account_commands.rs#L1931-L1967","documentation":"This error is logged when the frontend-invoked codex_oauth_completed command fails to finalize a Codex OAuth login: complete_oauth_login(&login_id) returned Err after the browser flow finished. The pending login state (matched by login_id and started_at_ms) could not be exchanged/validated into tokens, and the error is propagated back to the caller.","triggerScenarios":"complete_oauth_login(login_id) fails because the pending login expired or was never registered, the state token mismatched, the authorization code/token exchange with the Codex OAuth endpoint returned an error, or the callback server stored no result for that login_id.","commonSituations":"User took too long between starting login and completing authorization (pending state expired); user restarted the app between start and completed calls (in-memory pending login lost); clicking the login button twice so a stale login_id is completed; upstream auth server returning 4xx/5xx on token exchange.","solutions":["Restart the OAuth flow from the beginning (start a fresh login to get a new login_id) rather than retrying the completed call","Check the elapsed duration_ms in the log — a large value means the pending login likely expired; complete the flow faster","Ensure the app isn't restarted between the OAuth start and completion steps","Inspect the wrapped error detail to see if it's a state mismatch (double-click/duplicate flow) vs upstream token-exchange failure"],"exampleFix":"// before\nlet tokens = match codex_oauth::complete_oauth_login(&login_id).await {\n    Ok(tokens) => tokens,\n    Err(e) => {\n        logger::log_error(&format!(\"Codex OAuth completed 命令失败: login_id={}, duration_ms={}, error={}\", login_id, chrono::Utc::now().timestamp_millis() - started_at_ms, e));\n        return Err(e);\n    }\n};\n// after\nlet tokens = match codex_oauth::complete_oauth_login(&login_id).await {\n    Ok(tokens) => tokens,\n    Err(e) => {\n        logger::log_error(&format!(\"Codex OAuth completed 命令失败: login_id={}, duration_ms={}, error={}\", login_id, chrono::Utc::now().timestamp_millis() - started_at_ms, e));\n        // clear the dead pending state so the UI can start a clean flow immediately\n        let _ = codex_oauth::clear_pending_login(&login_id).await;\n        return Err(format!(\"OAuth 完成失败，请重新发起登录: {}\", e));\n    }\n};","handlingStrategy":"try-catch","validationCode":"// before calling completed, confirm the pending login still exists and is fresh\nif !codex_oauth::has_pending_login(&login_id) {\n    return Err(\"登录会话不存在或已失效，请重新发起登录\".to_string());\n}\nif started_at_ms.elapsed_ms() > PENDING_LOGIN_TTL_MS {\n    return Err(\"登录会话已超时，请重新发起登录\".to_string());\n}","typeGuard":null,"tryCatchPattern":"match codex_oauth::complete_oauth_login(&login_id).await {\n    Ok(tokens) => tokens,\n    Err(e) => {\n        let _ = codex_oauth::clear_pending_login(&login_id); // avoid stuck state\n        return Err(format!(\"OAuth 完成失败，请重新发起登录: {}\", e));\n    }\n}","preventionTips":["Complete the OAuth flow in one sitting; don't leave the browser waiting","Never restart the app between OAuth start and completion","Avoid double-clicking the login button (duplicate login_ids)","If it fails, always start a fresh login instead of retrying the completed call"],"tags":["codex","oauth","token-exchange","state-expired"],"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"}