{"record":{"id":"603bed723ed9b1ba","repo":"zeroclaw-labs/zeroclaw","slug":"device-code-flow-timed-out-before-authorization-co","errorCode":null,"errorMessage":"Device-code flow timed out before authorization completed","messagePattern":"Device-code flow timed out before authorization completed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-providers/src/auth/openai_oauth.rs","lineNumber":175,"sourceCode":"        user_code: parsed.user_code,\n        verification_uri: parsed.verification_uri,\n        verification_uri_complete: parsed.verification_uri_complete,\n        expires_in: parsed.expires_in,\n        interval: parsed.interval.unwrap_or(5).max(1),\n        message: parsed.message,\n    })\n}\n\npub async fn poll_device_code_tokens(\n    client: &Client,\n    device: &DeviceCodeStart,\n) -> Result<TokenSet> {\n    let started = Instant::now();\n    let mut interval_secs = device.interval.max(1);\n\n    loop {\n        if started.elapsed() > Duration::from_secs(device.expires_in) {\n            anyhow::bail!(\"Device-code flow timed out before authorization completed\");\n        }\n\n        tokio::time::sleep(Duration::from_secs(interval_secs)).await;\n\n        let form = [\n            (\"grant_type\", \"urn:ietf:params:oauth:grant-type:device_code\"),\n            (\"device_code\", device.device_code.as_str()),\n            (\"client_id\", OPENAI_OAUTH_CLIENT_ID),\n        ];\n\n        let response = client\n            .post(OPENAI_OAUTH_TOKEN_URL)\n            .form(&form)\n            .send()\n            .await\n            .context(\"Failed polling OpenAI device-code token endpoint\")?;\n\n        if response.status().is_success() {","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/openai_oauth.rs#L157-L193","documentation":"poll_device_code_tokens loops, sleeping device.interval seconds between token polls, and checks elapsed time against device.expires_in from the device-code response. If the user has not completed authorization before that lifetime elapses, it bails with this timeout. The device code is single-use and short-lived (typically a few minutes), so a late approval cannot be salvaged.","triggerScenarios":"Starting `--device-code` login, receiving the user_code/URL, and not visiting/authorizing it before expires_in; or the user approving after the poll loop already exited.","commonSituations":"Headless/servers where the URL is copy-pasted slowly, user steps away from the terminal, screen-sharing sessions with delays, or automation that surfaces the code too late.","solutions":["Restart the flow: run `zeroclaw auth login --model-provider openai-codex --device-code` again and authorize promptly at the verification URL","Copy the user_code immediately — it expires with the device code","If approval is slow by nature (approval queues), use the browser loopback flow instead so the redirect arrives when ready"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match openai_oauth::poll_device_code_tokens(&client, &device).await {\n    Err(e) if e.to_string().contains(\"timed out before authorization\") => {\n        // restart the whole flow with a fresh device code\n        start_device_code_flow(&client, &id, &secret, &scopes).await\n    }\n    other => other,\n}","preventionTips":["Authorize immediately after the user_code is displayed — codes live only minutes","Automate surfacing the verification URL (open browser, notify) so approval latency is low","Prefer the loopback browser flow when the approver is not sitting at the terminal"],"tags":["oauth2","device-code","openai","timeout","expired"],"backgroundTag":"oauth-device-code-timeout","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}