xai-org/grok-build · error · OidcError

OidcError::BindLoopback

Error message

OidcError::BindLoopback

What it means

The OIDC login flow binds a local TCP listener on 127.0.0.1 (fixed port 56121, or an ephemeral port 0) to receive the OAuth redirect. This error wraps the std::io error when `TcpListener::bind(("127.0.0.1", callback_port))` fails, so the flow cannot start the loopback callback server.

Source

Thrown at crates/codegen/xai-grok-shell/src/auth/oidc/login.rs:400

    )
    .ok();

    let discovery = discover(&oidc.issuer).await?;
    let pkce = generate_pkce();
    let state = uuid::Uuid::now_v7().to_string();
    let nonce = uuid::Uuid::now_v7().to_string();

    // In local-dev mode, use a fixed callback port so the redirect_uri is stable
    // and can be pre-registered with the local OAuth2 provider. In production the
    // OS picks a random available port.
    let callback_port: u16 = if super::super::config::use_local_auth() {
        56121
    } else {
        0
    };
    let listener = TcpListener::bind(("127.0.0.1", callback_port))
        .await
        .map_err(|e| anyhow::Error::new(OidcError::BindLoopback(e.to_string())))?;
    let port = listener.local_addr()?.port();
    let redirect_uri = format!("http://127.0.0.1:{}/callback", port);
    let oauth2 = auth_manager.grok_com_config().oauth2.as_ref();
    let auth_url = build_authorize_url(
        oidc,
        oauth2,
        &discovery,
        &redirect_uri,
        &pkce,
        &state,
        &nonce,
    );
    tracing::debug!(port = port, redirect_uri = %redirect_uri, "OIDC: callback server bound");

    let (url_tx, code_rx) = match channels {
        Some(ch) => (ch.url_tx, Some(ch.code_rx)),
        None => (None, None),
    };

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Find and kill the process holding the port: `lsof -i :56121` then terminate the stale process.
  2. Re-run the login — if the config uses port 0 (ephemeral), a retry picks a free port.
  3. Configure a different fixed callback port in the OIDC config, updating the IDP's allowed redirect_uri to match.
  4. Verify loopback networking is available (not blocked in the container/sandbox) and no firewall rule forbids binding to 127.0.0.1.

Example fix

// before
$ lsof -i :56121
COMMAND   PID USER ...
old-grok 1234 dev
$ kill 1234  # free the port, then retry login
// after
$ grok login  # binds 127.0.0.1:56121 successfully
Defensive patterns

Strategy: retry

Validate before calling

// preflight: check the fixed callback port is free before starting login
let port_free = std::net::TcpListener::bind(("127.0.0.1", 56121)).is_ok();
if !port_free {
    eprintln!("port 56121 is in use; kill the stale process or switch to an ephemeral port");
}

Type guard

fn is_bind_loopback(err: &anyhow::Error) -> bool {
    err.downcast_ref::<OidcError>()
        .map_or(false, |e| matches!(e, OidcError::BindLoopback(_)))
}

Try / catch

if is_bind_loopback(&err) {
    // common cause: address already in use — brief backoff then retry
    tokio::time::sleep(Duration::from_secs(2)).await;
    return run_login_flow(config, auth_manager, channels).await;
}

Prevention

When it happens

Trigger: TcpListener::bind on 127.0.0.1 fails in run_login_flow_with_config — typically port 56121 already in use, or binding to loopback is disallowed.

Common situations: Another instance of the tool (or a stale process) is still holding port 56121; another dev server occupies the port; container/sandbox with no loopback networking; strict firewall/SELinux policy; IPv6-only environment where 127.0.0.1 binding is restricted.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/c22535d78635b931. Report an issue: GitHub.