zeroclaw-labs/zeroclaw · warning · anyhow::Error

OpenAI device-code expired

Error message

OpenAI device-code expired

What it means

The token endpoint returned the OAuth error expired_token during device-code polling: the device code (and its user_code) exceeded its expires_in lifetime before authorization completed. This is the server-side counterpart of the client-side timeout (error 693) — here OpenAI itself declares the code dead.

Source

Thrown at crates/zeroclaw-providers/src/auth/openai_oauth.rs:213

        }

        let status = response.status();
        let text = response.text().await.unwrap_or_default();

        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&text) {
            match err.error.as_str() {
                "authorization_pending" => {
                    continue;
                }
                "slow_down" => {
                    interval_secs = interval_secs.saturating_add(5);
                    continue;
                }
                "access_denied" => {
                    anyhow::bail!("OpenAI device-code authorization was denied")
                }
                "expired_token" => {
                    anyhow::bail!("OpenAI device-code expired")
                }
                _ => {
                    anyhow::bail!(
                        "OpenAI device-code polling failed ({status}): {}",
                        err.error_description.unwrap_or(err.error)
                    )
                }
            }
        }

        anyhow::bail!("OpenAI device-code polling failed ({status}): {text}");
    }
}

pub async fn receive_loopback_code(expected_state: &str, timeout: Duration) -> Result<String> {
    ::zeroclaw_log::scope!(
        model_provider_type: "openai",
        model_provider_alias: "oauth",

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Start a fresh `zeroclaw auth login --model-provider openai-codex --device-code` and complete authorization within the displayed window
  2. Verify the machine clock is correct (NTP sync) so client and server lifetimes agree
  3. Prefer the browser loopback flow when approval latency is unpredictable
Defensive patterns

Strategy: retry

Try / catch

match openai_oauth::poll_device_code_tokens(&client, &device).await {
    Err(e) if e.to_string().contains("device-code expired") => {
        // server declared the code dead: fresh flow required
        start_device_code_flow(&client, &id, &secret, &scopes).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Polling continues (e.g. the elapsed check at the top of the loop has not tripped due to clock skew or a slow first poll) when the server reports the device code already expired — typically a user who started authorization far too late.

Common situations: Delayed approvals on headless machines, expired code pasted from an old terminal scrollback, or system clock drift between client and server.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/a706d13693ccaacb. Report an issue: GitHub.