xai-org/grok-build · error
auth entry has no refresh_token — cannot refresh expired tok
Error message
auth entry has no refresh_token — cannot refresh expired tokens
What it means
`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.
Source
Thrown at crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs:167
/// PersistGate. Offload the same write so a contended flock cannot stall
/// the runtime.
fn persist_on_refresh_off_thread(auth_path: PathBuf, scope_key: String) -> OnRefreshCallback {
let persist = persist_on_refresh(auth_path, scope_key);
Arc::new(move |event: &RefreshEvent| {
let persist = persist.clone();
let event = event.clone();
std::thread::spawn(move || persist(&event));
})
}
fn build_oidc_provider(
scope_key: String,
entry: &AuthEntry,
auth_path: PathBuf,
refresh_cfg: &ProactiveRefreshConfig,
) -> anyhow::Result<(Arc<dyn AuthProvider>, OidcProviderKind)> {
let refresh_token = entry.refresh_token.as_ref().ok_or_else(|| {
anyhow::anyhow!("auth entry has no refresh_token — cannot refresh expired tokens")
})?;
let issuer = entry.oidc_issuer.as_ref().ok_or_else(|| {
anyhow::anyhow!("auth entry has no oidc_issuer — cannot refresh expired tokens")
})?;
let client_id = entry.oidc_client_id.as_ref().ok_or_else(|| {
anyhow::anyhow!("auth entry has no oidc_client_id — cannot refresh expired tokens")
})?;
if refresh_cfg.enabled {
return Ok((
Arc::new(ProactiveOidcAuthProvider::new(ProactiveOidcParams {
access_token: entry.key.clone(),
refresh_token: refresh_token.clone(),
issuer: issuer.clone(),
client_id: client_id.clone(),
identity: identity_from_entry(entry),
expires_at: entry.expires_at,
refresh: refresh_cfg.clone(),View on GitHub (pinned to bc7f02eddd)
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.
Example fix
// before
{ "key": "sk-...", "oidc_issuer": "https://issuer", "oidc_client_id": "client" }
// after
{ "key": "sk-...", "refresh_token": "rt_...", "oidc_issuer": "https://issuer", "oidc_client_id": "client" } Defensive patterns
Strategy: validation
Validate before calling
if entry.refresh_token.is_none() {
anyhow::bail!("entry '{}' lacks refresh_token; run `grok login` to obtain OIDC credentials", scope_key);
} Type guard
fn is_refreshable(entry: &AuthEntry) -> bool {
entry.refresh_token.is_some()
} Try / catch
match build_oidc_provider(scope_key, &entry, auth_path.clone(), &cfg) {
Ok((provider, kind)) => use_provider(provider, kind),
Err(e) if e.to_string().contains("no refresh_token") => {
eprintln!("credentials missing refresh_token — re-run `grok login`");
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- auth entry has no oidc_issuer — cannot refresh expired token
- auth entry has no oidc_client_id — cannot refresh expired to
- no OIDC auth entry found in {}. Run `grok login` first.
- upload parked: credentials rejected (HTTP 401); retrying in
- ACP response missing result field
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/3bbb2a019e89364b.
Report an issue: GitHub.