xai-org/grok-build · error

OIDC expires_in out of range: {secs}

Error message

OIDC expires_in out of range: {secs}

What it means

After a token refresh exchange, the provider parses the OIDC `expires_in` (seconds) into an expiry timestamp. This error is raised as a non-terminal `RefreshError` when `expires_in` is missing or outside the representable/valid range, so no sane expiry can be computed. The rotated refresh token is kept in memory for retry but not persisted, to avoid clobbering a later successful write.

Source

Thrown at crates/codegen/xai-grok-workspace/src/hub_auth/proactive.rs:564

            .map_err(refresh_err)?,
    )
    .await?
    .json()
    .await
    .map_err(refresh_err)?;

    let now = Utc::now();
    let (observed_ttl, expires_at) = match tokens.expires_in {
        Some(secs) => {
            let ttl = Duration::from_secs(secs);
            match datetime_plus(now, ttl) {
                Some(exp) => (Some(ttl), Some(exp)),
                None => {
                    // Keep the rotated RT in memory for the next retry, but
                    // do not persist this failed exchange — a late write
                    // would clobber a later successful persist.
                    return Err(RefreshError {
                        error: anyhow::anyhow!("OIDC expires_in out of range: {secs}"),
                        new_refresh_token: tokens.refresh_token,
                        terminal: false,
                        retry_after: None,
                    });
                }
            }
        }
        None => (None, None),
    };
    let lead_secs = previous_expires_at.map(|exp| (exp - now).num_milliseconds() as f64 / 1000.0);

    persist_refresh_event(
        inner,
        &tokens.access_token,
        tokens.refresh_token.clone(),
        expires_at,
    );

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Retry the operation — the error is marked non-terminal and the rotated refresh token is retained for the next attempt.
  2. Verify the oidc_issuer points at a spec-compliant OIDC provider that returns a numeric `expires_in`.
  3. Check for proxies/interceptors (corporate MITM) mangling the token endpoint response.
  4. Inspect the raw token response with debug logging to see what `expires_in` value was actually returned.
Defensive patterns

Strategy: retry

Try / catch

// RefreshError is non-terminal here; retry after a short backoff
match provider.refresh().await {
    Ok(tokens) => use_tokens(tokens),
    Err(e) if !e.terminal => {
        tokio::time::sleep(Duration::from_secs(2)).await;
        // rotated refresh_token is carried in e.new_refresh_token for the retry
        provider.refresh().await?;
    }
    Err(e) => return Err(e.error),
}

Prevention

When it happens

Trigger: An OIDC token endpoint returns a refresh response whose `expires_in` is absent, zero, negative, or too large to convert into a `chrono::Duration`/future timestamp, during a proactive or on-demand refresh in ProactiveOidcAuthProvider.

Common situations: A misbehaving or non-standard OIDC provider/proxy returning malformed token responses; a captive portal or HTML error page parsed as JSON with unexpected fields; clock skew extremes making computed expiry invalid.

Related errors


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