xai-org/grok-build · error

auth entry has no oidc_issuer — cannot refresh expired token

Error message

auth entry has no oidc_issuer — cannot refresh expired tokens

What it means

`build_oidc_provider` requires `oidc_issuer` on the AuthEntry to know which OIDC token endpoint to call for refreshes. This error is thrown when the entry has a refresh_token but no `oidc_issuer`. The issuer is optional (`#[serde(default)]`) in the deserialized struct, so legacy files commonly lack it.

Source

Thrown at crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs:170

    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(),
                on_refresh: Some(persist_on_refresh(auth_path, scope_key)),
            })),
            OidcProviderKind::Proactive,

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Re-run `grok login` so the entry is rewritten with `oidc_issuer` populated.
  2. Manually add the correct `oidc_issuer` URL (e.g. https://auth.x.ai) to the auth.json entry if you know your provider.
  3. Confirm you are reading the intended auth.json (GROK_HOME/HOME) and not a legacy copy.

Example fix

// before
{ "key": "sk-...", "refresh_token": "rt_...", "oidc_client_id": "client" }
// after
{ "key": "sk-...", "refresh_token": "rt_...", "oidc_issuer": "https://auth.x.ai", "oidc_client_id": "client" }
Defensive patterns

Strategy: validation

Validate before calling

if entry.oidc_issuer.is_none() {
    anyhow::bail!("entry '{}' lacks oidc_issuer; refresh impossible — run `grok login`", scope_key);
}

Type guard

fn has_issuer(entry: &AuthEntry) -> bool {
    entry.oidc_issuer.as_deref().map_or(false, |s| s.starts_with("https://"))
}

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_issuer") => {
        eprintln!("auth.json entry predates OIDC schema — re-run `grok login`");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `build_oidc_provider` with an AuthEntry where `oidc_issuer` is `None` — typically an auth.json entry missing the optional `oidc_issuer` JSON key.

Common situations: auth.json written by a pre-OIDC CLI version; entries migrated or copied between machines with fields dropped; hand-edited credentials files.

Understand the failure class

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/17db4882733357c6. Report an issue: GitHub.