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

OpenAI device-code polling failed ({status}): {}

Error message

OpenAI device-code polling failed ({status}): {}

What it means

poll_device_code_tokens received a non-success token response whose body parsed as an OAuth error JSON; after handling slow_down, access_denied and expired_token specially, every other error code lands here with the HTTP status and the error_description (falling back to the error code). Typical culprits are invalid_grant (code already consumed/stale) or validation errors from a malformed request.

Source

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

        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",
        => async move {
            receive_loopback_code_inner(expected_state, timeout).await
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use the error_description in the message: invalid_grant usually means the code was consumed or superseded — restart `auth login --device-code` and use only the newest code
  2. Ensure only one login flow runs per machine/state-dir at a time
  3. For persistent validation errors, capture the status+body and report; then fall back to the browser loopback flow
Defensive patterns

Strategy: try-catch

Try / catch

match openai_oauth::poll_device_code_tokens(&client, &device).await {
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("invalid_grant") {
            anyhow::bail!("device code consumed or stale — restart the login flow");
        }
        Err(e)
    }
    ok => ok,
}

Prevention

When it happens

Trigger: Re-using or re-submitting a device code, a code_verifier mismatch (PKCE), or an IdP validation error during `--device-code` login — anything that is not slow_down/access_denied/expired_token.

Common situations: Two terminals running login flows simultaneously and codes getting crossed, retrying a poll after a network glitch re-sends a consumed code, or OpenAI changing validation rules for the device grant.

Related errors


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