{"record":{"id":"a37fd17d090837e1","repo":"jlcodes99/cockpit-tools","slug":"windsurf-oauth-login-id-error-a37fd1","errorCode":null,"errorMessage":"[Windsurf OAuth] 回调服务异常: login_id={}, error={}","messagePattern":"\\[Windsurf OAuth\\] 回调服务异常: login_id=(.+?), error=(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cockpit-core/src/modules/windsurf_oauth.rs","lineNumber":1072,"sourceCode":"    let pending = PendingOAuthState {\n        login_id: login_id.clone(),\n        state: state_token.clone(),\n        auth_url: auth_url.clone(),\n        callback_url: callback_url.clone(),\n        port,\n        created_at: now_timestamp(),\n        expires_at: now_timestamp() + OAUTH_TIMEOUT_SECONDS as i64,\n        access_token: None,\n        callback_error: None,\n    };\n\n    set_pending_login(Some(pending.clone()));\n\n    let callback_login_id = login_id.clone();\n    let callback_state = state_token.clone();\n    tokio::spawn(async move {\n        if let Err(e) = start_callback_server(port, callback_login_id, callback_state).await {\n            logger::log_error(&format!(\n                \"[Windsurf OAuth] 回调服务异常: login_id={}, error={}\",\n                login_id, e\n            ));\n        }\n    });\n\n    logger::log_info(&format!(\n        \"[Windsurf OAuth] 登录会话已创建: login_id={}, callback_url={}\",\n        pending.login_id, pending.callback_url\n    ));\n    Ok(to_start_response(&pending))\n}\n\npub async fn complete_login(login_id: &str) -> Result<WindsurfOAuthCompletePayload, String> {\n    hydrate_pending_login_if_missing();\n    let token = loop {\n        let state = {\n            let guard = PENDING_OAUTH_STATE","sourceCodeStart":1054,"sourceCodeEnd":1090,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/crates/cockpit-core/src/modules/windsurf_oauth.rs#L1054-L1090","documentation":"This error is logged when the local HTTP callback server used to receive the Windsurf OAuth redirect fails to start or aborts mid-flow inside a tokio::spawn task. The login record (pending login, state token) has already been staged, so the browser will complete the redirect but nothing is listening on the port, leaving the login hanging. It is a wrapper around the underlying io/BindError from start_callback_server.","triggerScenarios":"start_login() calls start_callback_server(port, login_id, state_token) in a spawned task and that future returns Err — e.g. the port is already bound by another instance or stale process, or the server task panics/errors while awaiting the callback.","commonSituations":"Running two login attempts concurrently on the same port; a previous crashed session left a listener on the port; firewall/security software blocking local socket binds; port collision with another dev tool listening on localhost.","solutions":["Check that the configured callback port is free (lsof -i :<port> / netstat) and kill the stale listener before retrying login","Retry the login — each start_login call picks a port; avoid launching two logins in parallel","Inspect the wrapped error in the log line to distinguish bind failure (port in use) from runtime failure","Update the app so the callback server uses an ephemeral port or retries on bind failure"],"exampleFix":"// before\nlet port = 14657;\ntokio::spawn(async move {\n    if let Err(e) = start_callback_server(port, callback_login_id, callback_state).await {\n        logger::log_error(&format!(\"[Windsurf OAuth] 回调服务异常: login_id={}, error={}\", login_id, e));\n    }\n});\n// after\n// bind is now attempted eagerly so the user gets an immediate error instead of a silent hang\nlet listener = TcpListener::bind((\"127.0.0.1\", port)).await\n    .map_err(|e| format!(\"回调端口 {} 被占用: {}\", port, e))?;\ntokio::spawn(async move {\n    if let Err(e) = start_callback_server_with_listener(listener, callback_login_id, callback_state).await {\n        logger::log_error(&format!(\"[Windsurf OAuth] 回调服务异常: login_id={}, error={}\", login_id, e));\n    }\n});","handlingStrategy":"validation","validationCode":"// before starting login, ensure the callback port is bindable\nasync fn callback_port_free(port: u16) -> bool {\n    tokio::net::TcpListener::bind((\"127.0.0.1\", port)).await.is_ok()\n}\nif !callback_port_free(port).await {\n    return Err(format!(\"回调端口 {} 已被占用，请关闭占用进程后重试\", port));\n}","typeGuard":null,"tryCatchPattern":"// the spawn swallows the error, so watch for the absence of a completion instead\nmatch tokio::time::timeout(Duration::from_secs(120), wait_for_login_result(&login_id)).await {\n    Ok(result) => result,\n    Err(_) => Err(\"登录超时：回调服务可能启动失败，请检查端口占用后重试\".to_string()),\n}","preventionTips":["Don't run two Windsurf logins concurrently","Kill leftover processes holding the callback port before logging in again","Check firewall/security software allows binding local ports","Upgrade to a build that binds an ephemeral port for the callback server"],"tags":["oauth","callback-server","port-bind","windsurf"],"backgroundTag":"oauth-callback-server-start-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"}