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
- Find and kill the process holding the port: `lsof -i :56121` then terminate the stale process.
- Re-run the login — if the config uses port 0 (ephemeral), a retry picks a free port.
- Configure a different fixed callback port in the OIDC config, updating the IDP's allowed redirect_uri to match.
- 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
- Check `lsof -i :56121` for stale processes before login
- Prefer ephemeral ports (callback_port = 0) when the IDP allows wildcard redirect URIs
- Ensure the environment permits loopback binding (containers, sandboxes, SELinux)
- Update the IDP redirect allowlist when changing the fixed port
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
- bind 127.0.0.1:{port}: {e}
- OIDC expires_in out of range: {secs}
- OIDC endpoint rejected request ({status}): {body}
- OidcError::DiscoveryHttp
- send failed: {body}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/c22535d78635b931.
Report an issue: GitHub.