xai-org/grok-build · error · OidcError

OidcError::CallbackTimeout

Error message

OidcError::CallbackTimeout

What it means

The OIDC loopback login flow waits for the browser/IDP to redirect to the local callback server with an authorization code. This error is thrown by `race_callback_and_client_ui` when the `tokio::time::timeout(AUTH_CALLBACK_TIMEOUT, ...)` wrapper expires (~10 minutes, per AUTH_CALLBACK_TIMEOUT) without any code arriving on the callback channel. It means the user never completed the browser authorization in the allotted window.

Source

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

        while let Some(code) = code_rx.recv().await {
            match parse_pasted_input(&code) {
                Ok(result) => {
                    tracing::debug!("OIDC: received code via client paste");
                    let _ = client_tx.send(Ok(result)).await;
                    return;
                }
                Err(e) => {
                    tracing::debug!(error = %e, "OIDC: invalid client paste input");
                }
            }
        }
    };

    drop(tx);

    let result = tokio::select! {
        r = tokio::time::timeout(AUTH_CALLBACK_TIMEOUT, rx.recv()) => {
            r.map_err(|_| anyhow::Error::new(OidcError::CallbackTimeout))?
                .ok_or_else(|| anyhow::Error::new(OidcError::CallbackChannelClosed))?
        }
        _ = client_bridge => {
            rx.recv().await
                .ok_or_else(|| anyhow::Error::new(OidcError::CallbackChannelClosed))?
        }
    };

    let _ = shutdown_tx.send(());
    let _ = server.await;

    result.map_err(|e| anyhow::Error::new(OidcError::CallbackAuthFailed(e)))
}

/// Race loopback callback against stdin paste.
async fn race_callback_and_stdin(
    listener: TcpListener,
    enable_stdin: bool,

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Re-run the login command and complete the browser authorization promptly (within ~10 minutes).
  2. Verify the redirect_uri (http://127.0.0.1:<port>/callback) was not blocked and the browser actually opened; if not, copy the printed URL manually.
  3. Check IDP availability/SSO latency; if 10 minutes is too short for your SSO flow, increase AUTH_CALLBACK_TIMEOUT and rebuild.
  4. Ensure the loopback port (56121 or ephemeral) is not firewalled so the callback can reach the local server.

Example fix

// before (timeout elapses with default)
const AUTH_CALLBACK_TIMEOUT: Duration = Duration::from_secs(600);
// after: allow more time for slow SSO flows
const AUTH_CALLBACK_TIMEOUT: Duration = Duration::from_secs(1800);
Defensive patterns

Strategy: try-catch

Validate before calling

let cfg = config.oidc.as_ref().ok_or("oidc not configured")?;
// preflight: check the authorize URL is reachable before starting the flow
reqwest::get(&format!("{}/.well-known/openid-configuration", cfg.issuer)).await?;

Type guard

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

Try / catch

match run_login_flow(config, auth_manager, channels).await {
    Ok((auth, created)) => /* ... */,
    Err(e) if is_callback_timeout(&e) => {
        eprintln!("Login timed out; re-run and complete browser auth within 10 minutes");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: tokio::time::timeout(AUTH_CALLBACK_TIMEOUT, rx.recv()) returns Err(Elapsed) while racing the loopback HTTP callback server against the client UI bridge in race_callback_and_client_ui, i.e. rx.recv() did not yield an authorization code within AUTH_CALLBACK_TIMEOUT.

Common situations: User opened the authorize URL but never finished login at the IDP; browser tab was closed; corporate SSO page hung; machine was suspended mid-login; user walked away from an interactive `login` command.

Understand the failure class

Related errors


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