xai-org/grok-build · error · OidcError

OidcError::CallbackAuthFailed

Error message

OidcError::CallbackAuthFailed

What it means

After a code was received in `race_callback_and_client_ui`, the flow performs the token exchange and any post-callback processing. If that inner `result` is Err, it is wrapped as OidcError::CallbackAuthFailed, preserving the underlying cause. This is not a transport failure — the callback arrived but turning it into tokens failed.

Source

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

    };

    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,
) -> anyhow::Result<Callback> {
    tracing::debug!(
        enable_stdin = enable_stdin,
        "OIDC: waiting for auth code (loopback + stdin)"
    );
    let (tx, mut rx) = tokio::sync::mpsc::channel::<CallbackResult>(1);
    let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();

    let app = build_callback_router(tx.clone());
    let server = tokio::spawn(async move {
        let _ = axum::serve(listener, app)
            .with_graceful_shutdown(async {

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Retry the login from scratch — authorization codes are single-use; a reused code yields invalid_grant.
  2. Verify client_id, client_secret, and redirect_uri exactly match the IDP application registration.
  3. Check PKCE: code_verifier must be the one bound to the authorize URL; don't re-run only the token step.
  4. Inspect the wrapped inner error (`e`) for the IDP's OAuth error description (e.g. invalid_grant, unauthorized_client).
  5. Ensure system clock is accurate (NTP) — large skew breaks token validation.

Example fix

// before: reusing a stale code fails
let tokens = exchange(code_from_previous_run)?;
// after: redo the full flow to obtain a fresh code
let (auth, _created) = run_login_flow(config, auth_manager, channels).await?;
Defensive patterns

Strategy: retry

Validate before calling

// preflight: verify OIDC config before starting the flow
let oidc = config.oidc.as_ref().ok_or("oidc not configured")?;
assert!(!oidc.client_id.is_empty(), "client_id must be set");
reqwest::get(format!("{}/.well-known/openid-configuration", oidc.issuer)).await?;

Type guard

fn is_callback_auth_failed(err: &anyhow::Error) -> Option<&anyhow::Error> {
    err.downcast_ref::<OidcError>()
        .and_then(|e| match e {
            OidcError::CallbackAuthFailed(inner) => Some(inner),
            _ => None,
        })
}

Try / catch

match run_login_flow(config, auth_manager, channels).await {
    Ok(res) => res,
    Err(e) if is_callback_auth_failed(&e).is_some() => {
        // codes are single-use: restart the whole flow for a fresh code
        run_login_flow(config, auth_manager, channels).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: result.map_err(|e| OidcError::CallbackAuthFailed(e)) at the end of race_callback_and_client_ui — the awaited token exchange / authorization-code processing returned an error.

Common situations: Authorization code already redeemed or expired; PKCE code_verifier mismatch; wrong client_id/client_secret; IDP returned an OAuth error (invalid_grant); clock skew invalidating tokens; IDP temporarily down.

Related errors


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