Hmbown/CodeWhale · error

unable to bind OAuth callback ports

Error message

unable to bind {name} OAuth callback ports

What it means

The OAuth flow binds one or more fixed loopback TCP ports to receive the provider's redirect. When every candidate port failed to bind, this error is thrown (wrapped with a context naming the required ports and a conflict hint). It means no listener could be opened, so sign-in cannot proceed.

Solutions

  1. Close the process occupying the callback port: lsof -i :<port> then kill it, or stop other Codewhale instances.
  2. Re-run the sign-in command; transient TIME_WAIT usually clears in seconds.
  3. Check the provider's callback_conflict_hint in the error context for port-specific guidance.
  4. If sandboxed (container, CI), ensure loopback TCP listening is permitted.

Example fix

// before: second instance while one is already signed in
codewhale auth login &   # port already held by first instance
// after
pkill -f 'codewhale auth' && codewhale auth login
Defensive patterns

Strategy: retry

Validate before calling

const free = await new Promise(r => { const s = require('net').createServer(); s.once('error', () => r(false)); s.listen(port, '127.0.0.1', () => s.close(() => r(true))); });

Try / catch

try { await signIn(); } catch (e) { if (String(e).includes('unable to bind')) { await new Promise(r => setTimeout(r, 1000)); return signIn(); } throw e; }

Prevention

When it happens

Trigger: Calling start_auth_request_on/bind for the provider's callback ports when all of them return bind errors (last_error is this message only when no underlying bind error was captured, i.e. the port list was empty or errors were dropped).

Common situations: Another instance of Codewhale (or another app like a dev server) already listening on the provider's fixed loopback ports; stale sign-in process holding the socket; running inside a container/sandbox where loopback binding is restricted; TIME_WAIT exhaustion.

Related errors


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

Appendix: source

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

        }
        if !bound.is_empty() {
            return Ok(bound);
        }
    }
    let ports = params
        .loopback_ports
        .iter()
        .map(u16::to_string)
        .collect::<Vec<_>>()
        .join(" or ");
    let hint = if params.callback_conflict_hint.is_empty() {
        String::new()
    } else {
        format!(" {}", params.callback_conflict_hint)
    };
    Err(last_error
        .map(anyhow::Error::from)
        .unwrap_or_else(|| anyhow::anyhow!("unable to bind {name} OAuth callback ports")))
    .with_context(|| format!("{name} sign-in needs loopback port {ports}.{hint}"))
}

pub(crate) fn start_auth_request_on(
    listeners: &[TcpListener],
    params: &OAuthProviderParams,
    inputs: &ResolvedOAuthInputs,
) -> Result<BrowserAuthRequest> {
    let port = listeners
        .first()
        .with_context(|| {
            format!(
                "{} OAuth callback has no bound listener",
                params.display_name
            )
        })?
        .local_addr()
        .with_context(|| {

View on GitHub (pinned to 73e0f67d83)