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

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

Error message

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

What it means

The catch-all branch of poll_device_code_tokens: the token poll returned a non-success status AND the body did not parse as a known OAuth error JSON (or was not an error object at all), so the raw response text is embedded in the message. This is how unexpected responses — 5xx HTML pages, gateway timeouts, proxy blocks, empty bodies — surface from the device flow.

Source

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

                    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
        }
    )
    .await
}

async fn receive_loopback_code_inner(expected_state: &str, timeout: Duration) -> Result<String> {
    let listener = TcpListener::bind("127.0.0.1:1455")
        .await
        .context("Failed to bind callback listener at 127.0.0.1:1455")?;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Inspect {status} and {text}: HTML/markup usually means proxy or incident page — check egress and https://status.openai.com
  2. Retry the login after the incident clears; the device flow is stateless to restart
  3. If a proxy is intercepting, add an exception for the OpenAI auth domains or use the browser loopback flow
Defensive patterns

Strategy: retry

Try / catch

let mut attempt = 0;
loop {
    match openai_oauth::poll_device_code_tokens(&client, &device).await {
        Err(e) if e.to_string().contains("polling failed") && attempt < 3 => {
            attempt += 1;
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
        }
        other => break other,
    }
}

Prevention

When it happens

Trigger: OpenAI token endpoint returning 500/502/503 with an HTML or empty body during an incident, or a corporate proxy/captive portal intercepting the POST during device-code login.

Common situations: Cloud/vendor incidents, rate-limit pages without JSON bodies, TLS-inspecting proxies rewriting responses, or DNS sinkholes.

Related errors


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