jdx/mise · error

GitHub OAuth token is not cached. Run `mise token github --o

Error message

GitHub OAuth token is not cached. Run `mise token github --oauth` to authorize.

What it means

mise's GitHub OAuth flow found no cached OAuth token usable for the request: either no token has ever been cached, or the cached token expired and device-flow authorization is disabled for this call (token_async bails when req.allow_device_flow is false and it cannot return or refresh a valid cached token). The token is only minted interactively via the GitHub device flow, so mise refuses to silently fall back to an unauthenticated or anonymous path. The message points at the exact command to fix it.

Source

Thrown at src/github/oauth.rs:278

        // even though the token is still time-valid.
        let stale_access_token = req.force_refresh.then_some(cached.access_token.as_str());
        match refresh_cached_token(&cache_key, stale_access_token).await {
            Ok(Some(token)) => return Ok(token),
            Ok(None) => {}
            Err(err) => {
                if req.warn_on_refresh_failure {
                    log_refresh_error(&err);
                } else {
                    debug!("failed to refresh GitHub OAuth token for environment export: {err:#}");
                }
            }
        }
        if !req.force_refresh && cached.expires_at > chrono::Utc::now() {
            return Ok(cached.access_token);
        }
    }
    if !req.allow_device_flow {
        bail!("GitHub OAuth token is not cached. Run `mise token github --oauth` to authorize.");
    }

    let device = create_device_code().await?;
    print_device_instructions(&device);
    let token = poll_access_token(&device).await?;
    let cached = token_response_to_cache(token)?;
    let access_token = cached.access_token.clone();
    if let Err(err) = cache_token(cache_key, cached).await {
        warn!("failed to cache GitHub OAuth token: {err:#}");
    }
    Ok(access_token)
}

async fn create_device_code() -> Result<DeviceCodeResponse> {
    let settings = Settings::get();
    let url = format!(
        "{}/device/code",
        settings.github.oauth_auth_url.trim_end_matches('/')

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Run `mise token github --oauth` once interactively to complete the device-flow authorization and populate the cache.
  2. If running in CI, authorize locally and copy the cached token, or use a GITHUB_TOKEN env var / token provider instead of OAuth.
  3. Re-check that the cache file was not deleted (fresh HOME, container rebuild) and persist it across CI runs via cache actions.
  4. If you expected a still-valid token, inspect the cached expires_at; re-authorize after expiry or ensure token refresh is possible.

Example fix

// before (CI, non-interactive)
mise token github
// error: token not cached

// after: authorize once locally
mise token github --oauth
Defensive patterns

Strategy: try-catch

Validate before calling

let cached = mise::github::oauth::peek_cached_token();
let authorized = cached.map_or(false, |t| t.expires_at > chrono::Utc::now());
if !authorized {
    eprintln!("run: mise token github --oauth");
}

Type guard

fn is_oauth_ready(cached: Option<&CachedToken>) -> bool {
    cached.map_or(false, |t| t.expires_at > chrono::Utc::now())
}

Try / catch

match mise::github::oauth::token(req) {
    Err(e) if e.to_string().contains("not cached") => {
        eprintln!("authorize first: mise token github --oauth");
    }
    Ok(t) => use_token(&t),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the GitHub token resolution (mise token github, or code paths using token()/token_async) when: (1) no OAuth token has been cached yet (`~`-local mise token cache is empty); (2) the cached token's expires_at is in the past and refresh failed/produced nothing, with allow_device_flow=false; (3) force_refresh was requested but no cache exists and device flow is disallowed.

Common situations: Running `mise token github` (without --oauth) on a fresh machine or in CI where the device flow was never completed; a non-interactive shell (CI, scripts) where mise disables the device flow; expired cached token after long disuse.

Related errors


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