{"record":{"id":"3a9986915b03dbca","repo":"jlcodes99/cockpit-tools","slug":"oauth-3a9986","errorCode":null,"errorMessage":"OAuth 回调服务器错误: {}","messagePattern":"OAuth 回调服务器错误: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/modules/codex_oauth.rs","lineNumber":234,"sourceCode":"    match TcpListener::bind((\"127.0.0.1\", state.port)) {\n        Ok(listener) => {\n            drop(listener);\n            let expected_state = state.state.clone();\n            let expected_login_id = state.login_id.clone();\n            let callback_url = state.redirect_uri.clone();\n            let app_handle_clone = app_handle.clone();\n            let port = state.port;\n            tokio::spawn(async move {\n                if let Err(e) = start_callback_server(\n                    port,\n                    expected_state,\n                    expected_login_id,\n                    callback_url,\n                    app_handle_clone,\n                )\n                .await\n                {\n                    logger::log_error(&format!(\"OAuth 回调服务器错误: {}\", e));\n                }\n            });\n            logger::log_info(&format!(\n                \"Codex OAuth 已恢复回调监听: login_id={}, port={}\",\n                state.login_id, state.port\n            ));\n        }\n        Err(err) if err.kind() == ErrorKind::AddrInUse => {\n            logger::log_info(&format!(\n                \"Codex OAuth 回调端口已占用，视为监听中: login_id={}, port={}\",\n                state.login_id, state.port\n            ));\n        }\n        Err(err) => {\n            logger::log_warn(&format!(\n                \"Codex OAuth 回调监听恢复失败: login_id={}, port={}, error={}\",\n                state.login_id, state.port, err\n            ));","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src-tauri/src/modules/codex_oauth.rs#L216-L252","documentation":"OAuth callback server failure log inside the restored listener spawned by ensure_callback_listener_for_state (src-tauri/src/modules/codex_oauth.rs:234). For a persisted OAuthState without a device_auth_id, the module re-binds the saved callback port and spawns start_callback_server on a tokio task; if that server task returns Err, this line logs it. The OAuth login flow then cannot receive the browser redirect callback, so the pending login will eventually time out. Called from start_oauth_login and restore_pending_oauth_listener.","triggerScenarios":"start_callback_server(port, expected_state, expected_login_id, callback_url, ...) returning Err after the pre-bind probe succeeded — e.g. another process steals port 127.0.0.1:callback_port between the probe and the server bind, the listener is closed unexpectedly, an internal accept/bind error occurs, or the tokio runtime fails to accept connections.","commonSituations":"Antivirus/firewall blocking loopback binds; another instance of the app (or Codex CLI) already holding the callback port in a state that passed the probe; port reused by an unrelated dev server on 127.0.0.1; the persisted OAuthState restored after app restart while a stale server still occupies the port; corporate security software intercepting loopback HTTP.","solutions":["Check whether another process is listening on the callback port (e.g. `lsof -iTCP:<port>` / `netstat -ano`) and kill it or change OAUTH_CALLBACK_PORT","Restart the OAuth login (start_oauth_login) to obtain a fresh state and re-establish the listener","Disable or configure antivirus/firewall software that blocks loopback socket binds","If the state expired, note that ensure_callback_listener_for_state clears it when expires_at <= now — just log in again","Retry after closing other instances of the app that may race for the same port"],"exampleFix":"// before: fire-and-forget callback server, failure only logged\ntokio::spawn(async move {\n    if let Err(e) = start_callback_server(port, state, login_id, url, handle).await {\n        log_error(\"OAuth 回调服务器错误: {}\", e);\n    }\n});\n// after: probe the bind inside the task and report the failure to the UI\ntokio::spawn(async move {\n    match start_callback_server(port, state, login_id, url, handle.clone()).await {\n        Ok(()) => {}\n        Err(e) => {\n            log_error(\"OAuth 回调服务器错误: {}\", e);\n            let _ = handle.emit(\"codex:oauth-callback-error\", serde_json::json!({\n                \"login_id\": login_id, \"port\": port, \"error\": e\n            }));\n        }\n    }\n});","handlingStrategy":"try-catch","validationCode":"use std::net::TcpListener;\nfn callback_port_available(port: u16) -> bool {\n    TcpListener::bind((\"127.0.0.1\", port)).map(|l| drop(l)).is_ok()\n}\n// check before starting/restoring OAuth login: callback_port_available(state.port)","typeGuard":"fn listener_restorable(state: &OAuthState) -> bool {\n    state.device_auth_id.is_none() && state.expires_at > now_timestamp()\n}","tryCatchPattern":"match start_oauth_login(app_handle).await {\n    Ok(login_id) => println!(\"OAuth 登录已启动: {login_id}\"),\n    Err(e) if e.contains(\"端口被占用\") || e.contains(\"port in use\") => {\n        eprintln!(\"回调端口被占用，请关闭占用进程后重试: {e}\");\n    }\n    Err(e) => eprintln!(\"OAuth 登录失败: {e}\"),\n}","preventionTips":["Check the callback port is free (and not held by another app instance) before initiating OAuth login","Configure antivirus/firewall to allow loopback binds on 127.0.0.1","Avoid running multiple app instances concurrently during OAuth login","If login seems stuck, restart the flow to get a fresh state before the persisted expires_at passes"],"tags":["oauth","callback-server","port-bind","network","codex"],"backgroundTag":"oauth-callback-server-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"}