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
- Re-run `grok login --device-auth` to obtain a fresh device code and complete it promptly.
- Open the verification_uri and enter the user_code as soon as the prompt appears.
- Check clock skew (NTP) if the code expires much earlier than expected.
- 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
- Authorize immediately when the verification URL appears.
- Surface expires_in to the user with a visible countdown.
- Ensure system clock sync (NTP) on headless machines.
- Prefer API-key or browser login for unattended automation.
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
- Server returned invalid user_code format (expected [A-Z0-9-]
- Authorization denied. The user rejected the request.
- Token exchange error: {detail}
- Server returned invalid verification URI
- Server returned unsupported verification URI scheme
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/a7833316d1355dca.
Report an issue: GitHub.