can1357/oh-my-pi · error · AIError.OAuthError
${label} missing expires_in
Error message
${label} missing expires_in What it means
Thrown by parseXAITokenResponse when expires_in is missing or not a finite number. The library computes the credential expiry timestamp (Date.now() + expires_in*1000, minus client skew) from this field; without it, token expiry cannot be tracked and proactive refresh would be impossible, so the response is rejected.
Source
Thrown at packages/ai/src/registry/oauth/xai-oauth.ts:348
}
const accessToken = typeof payload.access_token === "string" ? payload.access_token : "";
const responseRefreshToken = typeof payload.refresh_token === "string" ? payload.refresh_token : "";
const refreshToken = responseRefreshToken || refreshTokenFallback || "";
const expiresInSeconds = payload.expires_in;
if (!accessToken) {
throw new AIError.OAuthError(`${label} missing access_token`, {
kind: "validation",
provider: "xai",
});
}
if (!refreshToken) {
throw new AIError.OAuthError(`${label} missing refresh_token`, {
kind: "validation",
provider: "xai",
});
}
if (typeof expiresInSeconds !== "number" || !Number.isFinite(expiresInSeconds)) {
throw new AIError.OAuthError(`${label} missing expires_in`, {
kind: "validation",
provider: "xai",
});
}
return {
access: accessToken,
refresh: refreshToken,
expires: Date.now() + expiresInSeconds * 1000 - ACCESS_TOKEN_CLIENT_SKEW_MS,
};
}
async function requestXAIDeviceAuthorization(
fetchImpl: FetchImpl,
signal?: AbortSignal,
): Promise<XAIDeviceAuthorization> {
let response: Response;
try {
const timeoutSignal = AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS);View on GitHub (pinned to 9690622007)
Solutions
- Retry the exchange — a one-off malformed response is often transient.
- Inspect the raw token response with curl to see how expires_in is actually encoded.
- If your fetchImpl or middleware coerces numbers to strings, remove that transformation.
- Update the ai package if xAI changed the expires_in representation.
Example fix
// before: accepting a string expiry silently
const expiresIn = String(body.expires_in);
storeExpiry(expiresIn);
// after: require a finite number before computing expiry
if (typeof body.expires_in !== "number" || !Number.isFinite(body.expires_in)) {
throw new Error("token response missing numeric expires_in");
}
storeExpiry(Date.now() + body.expires_in * 1000); Defensive patterns
Strategy: validation
Validate before calling
// preflight: require a numeric expires_in before computing credential expiry
const body: Record<string, unknown> = await res.json();
const expiresIn = body.expires_in;
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn)) {
throw new Error(`xAI token response has invalid expires_in: ${JSON.stringify(expiresIn)}`);
}
const expiresAt = Date.now() + expiresIn * 1000; Type guard
function hasNumericExpiresIn(v: unknown): v is { expires_in: number } & Record<string, unknown> {
const n = (typeof v === "object" && v !== null) ? (v as Record<string, unknown>).expires_in : undefined;
return typeof n === "number" && Number.isFinite(n);
} Try / catch
try {
await xaiProvider.credentials();
} catch (err) {
if (err instanceof AIError.OAuthError && err.message.includes("missing expires_in")) {
// token usable but expiry untrackable — force a fresh login so expiry is known
await deleteStoredXAICredentials();
return xaiProvider.credentials();
}
throw err;
} Prevention
- Remove middleware that coerces JSON numbers to strings (common with schema-normalizing proxies).
- Spot-check the token endpoint's raw response with curl when setting up a new environment.
- Do not hand-normalize token payloads before handing them to the library.
- Update the ai package if xAI changes expires_in representation.
When it happens
Trigger: pollXAIDeviceToken gets a 200 body with access_token and refresh_token present but expires_in absent, null, a string (e.g. "3600"), or non-finite; or the same shape appears on the credentials refresh path.
Common situations: xAI endpoint variant returning expires_in as a string instead of a number; proxy/API-gateway stripping unknown fields; custom fetchImpl normalizing numbers to strings; xAI schema change.
Related errors
- xAI device-code response missing or invalid required fields.
- xAI device-code response was not a JSON object.
- ${label} was not a JSON object
- ${label} missing access_token
- ${label} missing refresh_token
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f61ce4bc823a6e2c.
Report an issue: GitHub.