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

  1. Run `grok login` to regenerate the entry with all OIDC fields.
  2. Add the correct `oidc_client_id` to the auth.json entry (it must match the client the refresh token was issued to).
  3. 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

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

Related errors


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