{"record":{"id":"c86663002d8a3b33","repo":"jdx/mise","slug":"github-oauth-token-is-not-cached-run-mise-token","errorCode":null,"errorMessage":"GitHub OAuth token is not cached. Run `mise token github --oauth` to authorize.","messagePattern":"GitHub OAuth token is not cached\\. Run `mise token github --oauth` to authorize\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/github/oauth.rs","lineNumber":278,"sourceCode":"        // even though the token is still time-valid.\n        let stale_access_token = req.force_refresh.then_some(cached.access_token.as_str());\n        match refresh_cached_token(&cache_key, stale_access_token).await {\n            Ok(Some(token)) => return Ok(token),\n            Ok(None) => {}\n            Err(err) => {\n                if req.warn_on_refresh_failure {\n                    log_refresh_error(&err);\n                } else {\n                    debug!(\"failed to refresh GitHub OAuth token for environment export: {err:#}\");\n                }\n            }\n        }\n        if !req.force_refresh && cached.expires_at > chrono::Utc::now() {\n            return Ok(cached.access_token);\n        }\n    }\n    if !req.allow_device_flow {\n        bail!(\"GitHub OAuth token is not cached. Run `mise token github --oauth` to authorize.\");\n    }\n\n    let device = create_device_code().await?;\n    print_device_instructions(&device);\n    let token = poll_access_token(&device).await?;\n    let cached = token_response_to_cache(token)?;\n    let access_token = cached.access_token.clone();\n    if let Err(err) = cache_token(cache_key, cached).await {\n        warn!(\"failed to cache GitHub OAuth token: {err:#}\");\n    }\n    Ok(access_token)\n}\n\nasync fn create_device_code() -> Result<DeviceCodeResponse> {\n    let settings = Settings::get();\n    let url = format!(\n        \"{}/device/code\",\n        settings.github.oauth_auth_url.trim_end_matches('/')","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/github/oauth.rs#L260-L296","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run `mise token github --oauth` once interactively to complete the device-flow authorization and populate the cache.","If running in CI, authorize locally and copy the cached token, or use a GITHUB_TOKEN env var / token provider instead of OAuth.","Re-check that the cache file was not deleted (fresh HOME, container rebuild) and persist it across CI runs via cache actions.","If you expected a still-valid token, inspect the cached expires_at; re-authorize after expiry or ensure token refresh is possible."],"exampleFix":"// before (CI, non-interactive)\nmise token github\n// error: token not cached\n\n// after: authorize once locally\nmise token github --oauth","handlingStrategy":"try-catch","validationCode":"let cached = mise::github::oauth::peek_cached_token();\nlet authorized = cached.map_or(false, |t| t.expires_at > chrono::Utc::now());\nif !authorized {\n    eprintln!(\"run: mise token github --oauth\");\n}","typeGuard":"fn is_oauth_ready(cached: Option<&CachedToken>) -> bool {\n    cached.map_or(false, |t| t.expires_at > chrono::Utc::now())\n}","tryCatchPattern":"match mise::github::oauth::token(req) {\n    Err(e) if e.to_string().contains(\"not cached\") => {\n        eprintln!(\"authorize first: mise token github --oauth\");\n    }\n    Ok(t) => use_token(&t),\n    Err(e) => return Err(e),\n}","preventionTips":["Run `mise token github --oauth` once per machine during setup, before CI or scripts need it.","Persist the token cache directory across CI runs with cache actions/volumes.","Prefer GITHUB_TOKEN env-based auth in non-interactive environments.","Check cached expires_at proactively and re-authorize before expiry."],"tags":["github","oauth","authentication","cli"],"backgroundTag":"authentication-required","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"}