jlcodes99/cockpit-tools · error

Codex OAuth 回调超时: login_id=

Error message

Codex OAuth 回调超时: login_id={}, callback_url={}, elapsed={}s

What it means

start_callback_server waits a bounded time for the browser to complete the Codex OAuth redirect. If the expected callback (with matching state/login_id) does not arrive before `timeout` elapses, it logs this message, sets clear_state_on_exit=true, and aborts the login session. It is the deliberate timeout path of the local OAuth flow, not an unexpected crash.

Solutions

  1. Retry the login and complete the browser consent within the timeout window
  2. Verify the redirect_uri/port used in the auth URL matches what start_callback_server listens on
  3. Ensure popups/external browser are allowed to open the authorization URL
  4. Check that the state/login_id is not being cleared by a concurrent login attempt
  5. Increase the timeout if slow networks or multi-account switching are common

Example fix

// before
if start.elapsed() > timeout {
    logger::log_error(&format!("Codex OAuth 回调超时: ..."));
    clear_state_on_exit = true;
}
// after
if start.elapsed() > timeout {
    logger::log_error(&format!("Codex OAuth 回调超时: ..."));
    clear_state_on_exit = true;
    // surface to caller so the UI can prompt a retry
    return Err(format!("Codex OAuth 登录超时 ({}s),请重试", timeout.as_secs()));
}
Defensive patterns

Strategy: retry

Validate before calling

// Before starting OAuth, verify a browser can open the auth URL and redirect_uri matches
assert_eq!(redirect_uri, format!("http://127.0.0.1:{}", port));
// and check no other login session holds the same state
if oauth_state_exists() { clear_oauth_state_if_matches(&state, &login_id); }

Try / catch

match start_oauth_login(app_handle).await {
    Err(e) if e.contains("超时") => {
        // prompt user: 'Login timed out — please retry and complete consent in the browser'
        retry_login_with_fresh_state()
    }
    Ok(r) => handle_response(r),
    Err(e) => eprintln!("{}", e),
}

Prevention

When it happens

Trigger: start_oauth_login (or ensure_callback_listener_for_state) started the listener, but the user never completed the browser consent, the redirect URL/port mismatched, or the callback POST/GET was dropped — so elapsed() > timeout fires.

Common situations: User leaves the consent tab open/pending or closes the browser; popup blocked so the auth page never opens; redirect_uri registered in the Codex OAuth app points to a different port so the local server never matches the state; system clock/sleep (laptop suspended) consuming the window; very short timeout configured.

Related errors


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

Appendix: source

Thrown at src-tauri/src/modules/codex_oauth.rs:858

    loop {
        let should_stop = {
            let oauth_state = OAUTH_STATE.lock().unwrap();
            match oauth_state.as_ref() {
                Some(state) => state.state != expected_state || state.login_id != expected_login_id,
                None => true,
            }
        };

        if should_stop {
            logger::log_info(&format!(
                "Codex OAuth 已取消或状态已变更,停止回调监听: login_id={}",
                expected_login_id
            ));
            break;
        }

        if start.elapsed() > timeout {
            logger::log_error(&format!(
                "Codex OAuth 回调超时: login_id={}, callback_url={}, elapsed={}s",
                expected_login_id,
                callback_url,
                start.elapsed().as_secs()
            ));
            clear_state_on_exit = true;
            break;
        }

        if let Ok(Some(request)) = server.try_recv() {
            let url = request.url().to_string();

            if url.starts_with("/auth/callback") {
                let has_query = url.contains('?');
                logger::log_info(&format!(
                    "Codex OAuth 收到回调请求: login_id={}, path=/auth/callback, has_query={}",
                    expected_login_id, has_query
                ));

View on GitHub (pinned to 1ed8b77992)