jdx/mise · error

GitHub device authorization expired

Error message

GitHub device authorization expired

What it means

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.

Source

Thrown at src/github/oauth.rs:329

        .error_for_status()?
        .json::<DeviceCodeResponse>()
        .await?)
}

async fn poll_access_token(device: &DeviceCodeResponse) -> Result<TokenResponse> {
    let settings = Settings::get();
    let deadline = chrono::Utc::now() + chrono::Duration::seconds(device.expires_in as i64);
    let mut interval = device.interval.max(1);
    let url = format!(
        "{}/oauth/access_token",
        settings.github.oauth_auth_url.trim_end_matches('/')
    );
    let client_id = settings.github.oauth_client_id.trim();
    let client = crate::http::HTTP.reqwest()?;

    loop {
        if chrono::Utc::now() >= deadline {
            bail!("GitHub device authorization expired");
        }
        tokio::time::sleep(Duration::from_secs(interval)).await;

        let response = match client
            .post(&url)
            .header("Accept", "application/json")
            .form(&[
                ("client_id", client_id),
                ("device_code", device.device_code.as_str()),
                ("grant_type", GRANT_DEVICE_CODE),
            ])
            .send()
            .await
            .and_then(|r| r.error_for_status())
        {
            Ok(resp) => match resp.json::<TokenResponse>().await {
                Ok(body) => body,
                Err(err) => {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Restart the flow: run `mise token github --oauth` again to get a fresh device code and enter it promptly.
  2. Open https://github.com/login/device as soon as the code is printed and complete authorization before the code expires.
  3. Avoid suspending the machine or killing the process mid-authorization; if interrupted, start over.
  4. If authorization is impossible in this environment, use a GITHUB_TOKEN environment variable instead of OAuth.

Example fix

// before
# started `mise token github --oauth`, entered code 20 minutes later
// GitHub device authorization expired

// after
# rerun and enter the code immediately at https://github.com/login/device
mise token github --oauth
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

loop {
    match token_async(req).await {
        Err(e) if e.to_string().contains("device authorization expired") => {
            if attempts >= 1 { return Err(e); }
            attempts += 1; // restart the flow with a fresh code
        }
        Ok(t) => break Ok(t),
        Err(e) => break Err(e),
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/4155620bce6ecba1. Report an issue: GitHub.