{"record":{"id":"3bbb2a019e89364b","repo":"xai-org/grok-build","slug":"auth-entry-has-no-refresh-token-cannot-refresh-e","errorCode":null,"errorMessage":"auth entry has no refresh_token — cannot refresh expired tokens","messagePattern":"auth entry has no refresh_token — cannot refresh expired tokens","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs","lineNumber":167,"sourceCode":"/// PersistGate. Offload the same write so a contended flock cannot stall\n/// the runtime.\nfn persist_on_refresh_off_thread(auth_path: PathBuf, scope_key: String) -> OnRefreshCallback {\n    let persist = persist_on_refresh(auth_path, scope_key);\n    Arc::new(move |event: &RefreshEvent| {\n        let persist = persist.clone();\n        let event = event.clone();\n        std::thread::spawn(move || persist(&event));\n    })\n}\n\nfn build_oidc_provider(\n    scope_key: String,\n    entry: &AuthEntry,\n    auth_path: PathBuf,\n    refresh_cfg: &ProactiveRefreshConfig,\n) -> anyhow::Result<(Arc<dyn AuthProvider>, OidcProviderKind)> {\n    let refresh_token = entry.refresh_token.as_ref().ok_or_else(|| {\n        anyhow::anyhow!(\"auth entry has no refresh_token — cannot refresh expired tokens\")\n    })?;\n    let issuer = entry.oidc_issuer.as_ref().ok_or_else(|| {\n        anyhow::anyhow!(\"auth entry has no oidc_issuer — cannot refresh expired tokens\")\n    })?;\n    let client_id = entry.oidc_client_id.as_ref().ok_or_else(|| {\n        anyhow::anyhow!(\"auth entry has no oidc_client_id — cannot refresh expired tokens\")\n    })?;\n\n    if refresh_cfg.enabled {\n        return Ok((\n            Arc::new(ProactiveOidcAuthProvider::new(ProactiveOidcParams {\n                access_token: entry.key.clone(),\n                refresh_token: refresh_token.clone(),\n                issuer: issuer.clone(),\n                client_id: client_id.clone(),\n                identity: identity_from_entry(entry),\n                expires_at: entry.expires_at,\n                refresh: refresh_cfg.clone(),","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs#L149-L185","documentation":"`build_oidc_provider` destructures the selected AuthEntry into the three fields required to construct an OIDC auth provider: `refresh_token`, `oidc_issuer`, and `oidc_client_id`. This first check fails when the entry has no `refresh_token`, so expired access tokens could never be refreshed. Unlike the read_auth_entry filter, this check also applies when the entry was supplied through a path that skipped the OIDC filter.","triggerScenarios":"Calling `build_oidc_provider` (indirectly via the `provider` entry point) with an AuthEntry whose `refresh_token` field is `None` — e.g. an API-key-only entry in auth.json, or an entry deserialized from JSON lacking the optional `refresh_token` key.","commonSituations":"auth.json written by an older `grok` version or another tool that only stores the access key; manual edits stripping the token; a login flow that stored only the access token because the OIDC exchange failed partway.","solutions":["Run `grok login` again so a fresh entry with a refresh_token is written to auth.json.","Check that the auth.json entry selected for your hub_url actually contains a `refresh_token` string.","If you construct AuthEntry programmatically (tests/tools), populate `refresh_token` before calling build_oidc_provider."],"exampleFix":"// before\n{ \"key\": \"sk-...\", \"oidc_issuer\": \"https://issuer\", \"oidc_client_id\": \"client\" }\n// after\n{ \"key\": \"sk-...\", \"refresh_token\": \"rt_...\", \"oidc_issuer\": \"https://issuer\", \"oidc_client_id\": \"client\" }","handlingStrategy":"validation","validationCode":"if entry.refresh_token.is_none() {\n    anyhow::bail!(\"entry '{}' lacks refresh_token; run `grok login` to obtain OIDC credentials\", scope_key);\n}","typeGuard":"fn is_refreshable(entry: &AuthEntry) -> bool {\n    entry.refresh_token.is_some()\n}","tryCatchPattern":"match build_oidc_provider(scope_key, &entry, auth_path.clone(), &cfg) {\n    Ok((provider, kind)) => use_provider(provider, kind),\n    Err(e) if e.to_string().contains(\"no refresh_token\") => {\n        eprintln!(\"credentials missing refresh_token — re-run `grok login`\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Refresh tokens are the source of long-lived auth — never strip them when editing auth.json.","After any failed login attempt, re-login rather than reusing the partial entry.","Prefer building AuthEntry via the library's login/deserialize path instead of constructing it by hand."],"tags":["auth","oidc","missing-field","rust"],"backgroundTag":"missing-oidc-credentials","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}