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
- Retry the operation — the error is marked non-terminal and the rotated refresh token is retained for the next attempt.
- Verify the oidc_issuer points at a spec-compliant OIDC provider that returns a numeric `expires_in`.
- Check for proxies/interceptors (corporate MITM) mangling the token endpoint response.
- 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
- Point oidc_issuer at a spec-compliant OIDC provider; avoid hand-rolled token endpoints.
- Watch for MITM proxies or captive portals that corrupt token responses.
- Keep host clocks NTP-synced so computed expiries stay in range.
- Log raw token-endpoint responses when debugging to catch malformed expires_in early.
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
- OIDC endpoint rejected request ({status}): {body}
- grove daemon unreachable
- {}
- {} failed: {}
- runtime-socket deny resolution failed: {error}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/0a620d6fa69dd386.
Report an issue: GitHub.