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

  1. Run `grok login` again so a fresh entry with a refresh_token is written to auth.json.
  2. Check that the auth.json entry selected for your hub_url actually contains a `refresh_token` string.
  3. 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

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

Related errors


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