xai-org/grok-build · error
auth entry has no oidc_client_id — cannot refresh expired to
Error message
auth entry has no oidc_client_id — cannot refresh expired tokens
What it means
`build_oidc_provider` requires `oidc_client_id` because OAuth2 refresh-token grants are issued per client_id. This error is thrown when the AuthEntry has a refresh_token and issuer but no `oidc_client_id`. Like the other OIDC fields it is optional during deserialization, so partially-populated entries surface here.
Source
Thrown at crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs:173
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(),
on_refresh: Some(persist_on_refresh(auth_path, scope_key)),
})),
OidcProviderKind::Proactive,
));
}
View on GitHub (pinned to bc7f02eddd)
Solutions
- Run `grok login` to regenerate the entry with all OIDC fields.
- Add the correct `oidc_client_id` to the auth.json entry (it must match the client the refresh token was issued to).
- If copying auth.json between machines, copy the complete entry object, not just the tokens.
Example fix
// before
{ "key": "sk-...", "refresh_token": "rt_...", "oidc_issuer": "https://auth.x.ai" }
// after
{ "key": "sk-...", "refresh_token": "rt_...", "oidc_issuer": "https://auth.x.ai", "oidc_client_id": "grok-cli" } Defensive patterns
Strategy: validation
Validate before calling
if entry.oidc_client_id.is_none() {
anyhow::bail!("entry '{}' lacks oidc_client_id; OAuth2 refresh grant requires it", scope_key);
} Type guard
fn has_client_id(entry: &AuthEntry) -> bool {
entry.oidc_client_id.as_deref().map_or(false, |s| !s.is_empty())
} Try / catch
match build_oidc_provider(scope_key, &entry, auth_path, &cfg) {
Ok((provider, kind)) => use_provider(provider, kind),
Err(e) if e.to_string().contains("no oidc_client_id") => {
eprintln!("auth.json entry incomplete — re-run `grok login` to regenerate all OIDC fields");
}
Err(e) => return Err(e),
} Prevention
- Copy auth.json entries atomically and completely between environments.
- Validate the entry has refresh_token + oidc_issuer + oidc_client_id at session start, not at first refresh.
- Never script-generate auth.json with partial field sets.
When it happens
Trigger: Calling `build_oidc_provider` with an AuthEntry where `oidc_client_id` is `None` — an auth.json entry missing the optional `oidc_client_id` key after refresh_token and oidc_issuer are present.
Common situations: Partial writes from an interrupted login; older auth.json schema without client_id; manual assembly of auth.json by scripts that only copy token fields.
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
- no OIDC auth entry found in {}. Run `grok login` first.
- auth entry has no refresh_token — cannot refresh expired tok
- upload parked: credentials rejected (HTTP 401); retrying in
- Failed to load config: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/cbb9f237c5f77248.
Report an issue: GitHub.