xai-org/grok-build · error

Device code expired. Run `grok login --device-auth` again.

Error message

Device code expired. Run `grok login --device-auth` again.

What it means

complete_device_code_login polls the token endpoint until a deadline computed from the device code's expires_in. If the deadline passes before the user completes authorization, the client stops polling and tells the user to restart the device login, because the server would reject the device_code anyway.

Source

Thrown at crates/codegen/xai-grok-shell/src/auth/device_code.rs:229

    surface: ClientSurface,
) -> anyhow::Result<(GrokAuth, bool)> {
    let client = crate::http::shared_client();
    let token_url = format!("{}/oauth2/token", issuer.trim_end_matches('/'));
    let mut poll_interval = std::time::Duration::from_secs(device_code.interval.max(1) as u64);
    let deadline = tokio::time::Instant::now()
        + std::time::Duration::from_secs(
            device_code
                .expires_in
                .max(MIN_DEVICE_CODE_EXPIRY_FALLBACK_SECS) as u64,
        );

    loop {
        // Sleep first: an immediate poll on a fresh code only returns
        // authorization_pending (and risks slow_down).
        tokio::time::sleep(poll_interval).await;

        if tokio::time::Instant::now() > deadline {
            anyhow::bail!("Device code expired. Run `grok login --device-auth` again.");
        }

        let resp = with_alpha_test_key(
            client
                .post(&token_url)
                .header("x-grok-client-version", xai_grok_version::VERSION)
                .header("x-grok-client-surface", surface.as_str())
                .form(&[
                    ("grant_type", DEVICE_GRANT_TYPE),
                    ("device_code", device_code.device_code.as_str()),
                    ("client_id", client_id),
                ]),
            &token_url,
        )
        .send()
        .await?;

        if resp.status().is_success() {

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Re-run `grok login --device-auth` to obtain a fresh device code and complete it promptly.
  2. Open the verification_uri and enter the user_code as soon as the prompt appears.
  3. Check clock skew (NTP) if the code expires much earlier than expected.
  4. For automation, pre-authorize via a non-interactive auth method (XAI_API_KEY or standard `grok login`) instead of device flow.

Example fix

// before: start flow, get distracted, poll expires
// after: restart promptly
grok login --device-auth
# open https://x.ai/device and enter the new code within the expiry window
Defensive patterns

Strategy: retry

Validate before calling

// nothing to check pre-call; but honor expires_in so you can warn the user early
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(resp.expires_in);

Try / catch

loop {
    match complete_device_code_login(&client, &pending).await {
        Err(e) if e.to_string().contains("Device code expired") => {
            eprintln!("Code expired; restarting device login...");
            pending = request_device_code(&client, &cfg).await?;
        }
        Ok(auth) => break auth,
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: complete_device_code_login's poll loop exceeds `deadline` (now > deadline) while the token endpoint keeps returning authorization_pending or slow_down — raised by request_device_code callers run_device_code_login_channels and prompt_and_poll.

Common situations: User waits too long to open the verification URL or enter the code; polling repeatedly hits slow_down and the interval grows past the expiry; device flow started headlessly and the user never saw the prompt.

Related errors


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