{"record":{"id":"4155620bce6ecba1","repo":"jdx/mise","slug":"github-device-authorization-expired","errorCode":null,"errorMessage":"GitHub device authorization expired","messagePattern":"GitHub device authorization expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/github/oauth.rs","lineNumber":329,"sourceCode":"        .error_for_status()?\n        .json::<DeviceCodeResponse>()\n        .await?)\n}\n\nasync fn poll_access_token(device: &DeviceCodeResponse) -> Result<TokenResponse> {\n    let settings = Settings::get();\n    let deadline = chrono::Utc::now() + chrono::Duration::seconds(device.expires_in as i64);\n    let mut interval = device.interval.max(1);\n    let url = format!(\n        \"{}/oauth/access_token\",\n        settings.github.oauth_auth_url.trim_end_matches('/')\n    );\n    let client_id = settings.github.oauth_client_id.trim();\n    let client = crate::http::HTTP.reqwest()?;\n\n    loop {\n        if chrono::Utc::now() >= deadline {\n            bail!(\"GitHub device authorization expired\");\n        }\n        tokio::time::sleep(Duration::from_secs(interval)).await;\n\n        let response = match client\n            .post(&url)\n            .header(\"Accept\", \"application/json\")\n            .form(&[\n                (\"client_id\", client_id),\n                (\"device_code\", device.device_code.as_str()),\n                (\"grant_type\", GRANT_DEVICE_CODE),\n            ])\n            .send()\n            .await\n            .and_then(|r| r.error_for_status())\n        {\n            Ok(resp) => match resp.json::<TokenResponse>().await {\n                Ok(body) => body,\n                Err(err) => {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/github/oauth.rs#L311-L347","documentation":"During the GitHub OAuth device-flow polling loop, poll_access_token enforces a deadline derived from the device-code response (github_in / expires_in). If GitHub keeps returning authorization_pending past that deadline, mise bails with 'GitHub device authorization expired' instead of polling forever. This mirrors GitHub's own expired_token error but is enforced locally.","triggerScenarios":"poll_access_token (via token_async) polls the GitHub oauth/access_token endpoint in a loop; every iteration where chrono::Utc::now() >= deadline bails. Triggered when the user does not enter the code within the device code's validity window (typically ~15 minutes), or polling stalls/sleeps long enough (slow_down increments) to blow past the deadline.","commonSituations":"User walks away during `mise token github --oauth` and enters the code after it expired; machine suspended mid-poll; very slow network repeatedly delaying polls past the window.","solutions":["Restart the flow: run `mise token github --oauth` again to get a fresh device code and enter it promptly.","Open https://github.com/login/device as soon as the code is printed and complete authorization before the code expires.","Avoid suspending the machine or killing the process mid-authorization; if interrupted, start over.","If authorization is impossible in this environment, use a GITHUB_TOKEN environment variable instead of OAuth."],"exampleFix":"// before\n# started `mise token github --oauth`, entered code 20 minutes later\n// GitHub device authorization expired\n\n// after\n# rerun and enter the code immediately at https://github.com/login/device\nmise token github --oauth","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"loop {\n    match token_async(req).await {\n        Err(e) if e.to_string().contains(\"device authorization expired\") => {\n            if attempts >= 1 { return Err(e); }\n            attempts += 1; // restart the flow with a fresh code\n        }\n        Ok(t) => break Ok(t),\n        Err(e) => break Err(e),\n    }\n}","preventionTips":["Enter the device code at github.com/login/device as soon as it is printed.","Do not suspend the machine or kill the process during authorization.","Keep polling under the code's validity window; avoid artificial long sleeps in wrappers.","Only run one device-flow authorization at a time."],"tags":["github","oauth","device-flow","timeout"],"backgroundTag":"request-timeout","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}