jlcodes99/cockpit-tools · error

[Windsurf OAuth] 回调服务恢复失败: login_id={}, error={}

Error message

[Windsurf OAuth] 回调服务恢复失败: login_id={}, error={}

What it means

Logged by the spawned task in ensure_callback_server_for_state when start_callback_server fails while (re)starting the local OAuth callback HTTP listener. This path runs for pending OAuth logins restored at startup (restore_pending_oauth_listener) or from start_login, so a failure means the Windsurf OAuth flow cannot receive its redirect and that login will not complete. The error value comes from binding/serving the callback port.

Source

Thrown at crates/cockpit-core/src/modules/windsurf_oauth.rs:126

        clear_pending_if_matches(&state.login_id, &state.state);
        return;
    }
    if state.access_token.is_some() || state.callback_error.is_some() {
        return;
    }

    match TcpListener::bind(("127.0.0.1", state.port)) {
        Ok(listener) => {
            drop(listener);
            let callback_login_id = state.login_id.clone();
            let callback_state = state.state.clone();
            let callback_port = state.port;
            tokio::spawn(async move {
                if let Err(e) =
                    start_callback_server(callback_port, callback_login_id.clone(), callback_state)
                        .await
                {
                    logger::log_error(&format!(
                        "[Windsurf OAuth] 回调服务恢复失败: login_id={}, error={}",
                        callback_login_id, e
                    ));
                }
            });
            logger::log_info(&format!(
                "[Windsurf OAuth] 已恢复本地回调服务: login_id={}, port={}",
                state.login_id, state.port
            ));
        }
        Err(err) if err.kind() == ErrorKind::AddrInUse => {
            logger::log_info(&format!(
                "[Windsurf OAuth] 本地回调端口已占用,视为监听中: login_id={}, port={}",
                state.login_id, state.port
            ));
        }
        Err(err) => {
            logger::log_warn(&format!(

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Check the logged error text: 'Address already in use' means find and kill the process holding the port (lsof -i :<port>).
  2. Clear/restart the pending OAuth login so a fresh login_id and callback state are used instead of the stale one.
  3. Restart cockpit after the port is free, or configure a different callback port if the mechanism supports it.
  4. Retry the Windsurf login from scratch once the listener can bind; the restored flow is dead once this fires.

Example fix

// before: blindly reusing the saved port from prior state
let callback_port = state.port;
tokio::spawn(start_callback_server(callback_port, ...));
// after: detect bind failure and surface it so the user re-logs-in
if !port_is_free(callback_port) {
    logger::log_error("callback port busy; cancel pending login and start a new one");
    return;
}
Defensive patterns

Strategy: fallback

Validate before calling

fn port_is_free(port: u16) -> bool {
    std::net::TcpListener::bind(("127.0.0.1", port)).is_ok()
}
if !port_is_free(state.port) {
    eprintln!("callback port {} busy — cancel pending login and retry", state.port);
}

Try / catch

match start_callback_server(callback_port, callback_login_id, callback_state).await {
    Ok(()) => {}
    Err(e) => {
        logger::log_error(&format!(
            "[Windsurf OAuth] 回调服务恢复失败: login_id={}, error={}",
            callback_login_id, e
        ));
        // mark this login_id as failed so the UI prompts a fresh login
    }
}

Prevention

When it happens

Trigger: Calling start_login or restore_pending_oauth_listener when the saved callback port cannot be bound: another process already listens on that port, the process lacks permission to bind, or the listener fails while serving the callback request.

Common situations: A previous cockpit instance (or zombie process) still holding the callback port after a crash; restarting the app before the old socket is released (TIME_WAIT); the port colliding with another local dev server; running in a sandbox that forbids binding the saved port.

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/1e9afc729af8513a. Report an issue: GitHub.