{"record":{"id":"307cc83bffb94971","repo":"zeroclaw-labs/zeroclaw","slug":"github-device-authorization-expired","errorCode":null,"errorMessage":"GitHub device authorization expired","messagePattern":"GitHub device authorization expired","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/copilot.rs","lineNumber":600,"sourceCode":"                    \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\"\n                }))\n                .send()\n                .await?\n                .json()\n                .await?;\n\n            if let Some(token) = token_response.access_token {\n                eprintln!(\"Authentication succeeded.\\n\");\n                return Ok(token);\n            }\n\n            match token_response.error.as_deref() {\n                Some(\"slow_down\") => {\n                    poll_interval += Duration::from_secs(5);\n                }\n                Some(\"authorization_pending\") | None => {}\n                Some(\"expired_token\") => {\n                    anyhow::bail!(\"GitHub device authorization expired\")\n                }\n                Some(error) => anyhow::bail!(\"GitHub auth failed: {error}\"),\n            }\n        }\n\n        anyhow::bail!(\"Timed out waiting for GitHub authorization\")\n    }\n\n    /// Exchange a GitHub access token for a Copilot API key.\n    async fn exchange_for_api_key(&self, access_token: &str) -> anyhow::Result<ApiKeyInfo> {\n        let mut request = self.http_client().get(GITHUB_API_KEY_URL);\n        for (header, value) in &Self::COPILOT_HEADERS {\n            request = request.header(*header, *value);\n        }\n        request = request.header(\"Authorization\", format!(\"token {access_token}\"));\n\n        let response = request.send().await?;\n","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/copilot.rs#L582-L618","documentation":"GitHub's OAuth device flow returns expired_token when the user_code shown at github.com/login/device was not confirmed within expires_in (typically about 15 minutes). The poll loop in device_code_login aborts instead of continuing to poll a dead code, so login must be restarted.","triggerScenarios":"device_code_login polls GITHUB_ACCESS_TOKEN_URL; the response error field is expired_token because nobody entered and approved the code in the browser before the deadline. Only raised during first-time Copilot login (cached tokens skip the flow).","commonSituations":"Unattended or headless login left waiting; user steps away from the browser; slow relaying of the code to the user; reusing a stale login attempt.","solutions":["Re-run the Copilot login and complete the browser prompt within the window (~15 minutes)","Copy the user_code exactly from the terminal when prompted at github.com/login/device","For headless machines, authenticate once elsewhere and copy the token dir, or configure a GitHub token in config instead of device flow","Keep the terminal session alive - killing it discards the pending device code"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const MAX_ATTEMPTS: usize = 3;\nfor attempt in 1..=MAX_ATTEMPTS {\n    match copilot_login().await {\n        Ok(token) => break_ok(token),\n        Err(e) if e.to_string().contains(\"device authorization expired\") => {\n            if attempt == MAX_ATTEMPTS { return Err(e); }\n            continue; // fresh device code, user gets a new window\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Show the user_code and URL prominently and complete the browser step immediately","Run interactive logins on machines with a browser reachable by the user","For headless boxes, authenticate once interactively and copy the token dir","Do not leave device-flow sessions unattended past the ~15 minute window"],"tags":["github","oauth","device-flow","copilot","rust"],"backgroundTag":"device-flow-authorization-timeout","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}