Hmbown/CodeWhale · error

sign-in timed out waiting for the browser callback

Error message

{} sign-in timed out waiting for the browser callback

What it means

The local loopback HTTP listener waited `CALLBACK_TIMEOUT` for the browser to complete sign-in and call back with the OAuth code, and the deadline expired. The library polls all resolved localhost stacks for the redirect; if none arrives in time the login is abandoned with this error.

Solutions

  1. Re-run the sign-in and complete the browser flow before the timeout.
  2. Open the printed authorize URL manually in a local browser if none launched (e.g. over SSH, use port forwarding).
  3. Verify nothing else occupies the loopback callback port and that a firewall/proxy isn't blocking `localhost` callbacks.
Defensive patterns

Strategy: retry

Try / catch

match pkce_login(provider).await {
    Err(e) if e.to_string().contains("timed out waiting for the browser callback") => {
        // retry once, or instruct user to open the printed URL manually
    }
    other => other,
}

Prevention

When it happens

Trigger: `pkce_login`'s callback wait loop reaches `Instant::now() >= deadline` before any callback request is received — the browser never opened, the user never finished sign-in, or the redirect went to the wrong port/loopback family.

Common situations: Browser failed to launch in a headless/SSH environment; user walked away and didn't complete sign-in; popup blocker swallowed the tab; the authorize URL's redirect port was blocked by another process or firewall; slow provider login page exceeded the timeout.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1f37fa460f936f1f. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:1373

        pkce,
        redirect_uri,
        authorize_url,
    })
}

const CALLBACK_TIMEOUT: Duration = Duration::from_secs(300);
const CALLBACK_HTML_OK: &str = "<!doctype html><html><body><p>Signed in to Codewhale. You can close this tab.</p></body></html>";
const CALLBACK_HTML_ERR: &str = "<!doctype html><html><body><p>Sign-in did not complete. You can close this tab and retry in Codewhale.</p></body></html>";

fn wait_for_callback(
    listeners: &[TcpListener],
    params: &OAuthProviderParams,
    expected_state: &str,
) -> Result<String> {
    let deadline = Instant::now() + CALLBACK_TIMEOUT;
    loop {
        if Instant::now() >= deadline {
            bail!(
                "{} sign-in timed out waiting for the browser callback",
                params.display_name
            );
        }
        // Whichever stack `localhost` resolved to for the browser is the one
        // that gets the connection; poll them all.
        for listener in listeners {
            match listener.accept() {
                Ok((stream, _)) => {
                    return handle_callback_stream(stream, params, expected_state);
                }
                Err(error)
                    if error.kind() == std::io::ErrorKind::WouldBlock
                        || error.kind() == std::io::ErrorKind::Interrupted => {}
                Err(error) => {
                    return Err(error).context(format!(
                        "{} OAuth callback accept failed",
                        params.display_name

View on GitHub (pinned to 73e0f67d83)