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
- 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
- Ensure only one login flow runs per machine/state-dir at a time
- 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
- Run exactly one device login per state dir at a time so codes never get crossed
- Log the error_description — it is the authoritative cause from the IdP
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
- OpenAI device-code start failed ({status}): {body}
- OpenAI device-code polling failed ({status}): {text}
- Device-code flow timed out before authorization completed
- OpenAI device-code authorization was denied
- OpenAI device-code expired
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/a3577c35416d7d85.
Report an issue: GitHub.