can1357/oh-my-pi · error · AIError.OAuthError
${label} missing refresh_token
Error message
${label} missing refresh_token What it means
Thrown by parseXAITokenResponse when neither the response body nor the fallback (e.g. the refresh token supplied by the caller during a refresh exchange) yields a non-empty refresh token. The library persists a refresh token so sessions can be renewed; a response without one would silently break future refreshes, so it is rejected. Note the fallback: during refresh, a response that omits refresh_token is fine if the caller passed the old one.
Source
Thrown at packages/ai/src/registry/oauth/xai-oauth.ts:342
function parseXAITokenResponse(payload: unknown, label: string, refreshTokenFallback?: string): OAuthCredentials {
if (!isRecord(payload)) {
throw new AIError.OAuthError(`${label} was not a JSON object`, {
kind: "validation",
provider: "xai",
});
}
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(View on GitHub (pinned to 9690622007)
Solutions
- For refresh flows, pass the existing refresh token as refreshTokenFallback so a non-rotating response still succeeds.
- Re-run device login to get a fresh token set that includes a refresh_token.
- Inspect the raw token response — if xAI no longer returns refresh_token for your grant type, update the ai package.
- Check account/product entitlements (SuperGrok plan) — some tiers may issue tokens without refresh capability.
Example fix
// before: refresh call that drops the old refresh token const creds = await refreshXAIToken(old.access, old.expires); // after: supply the old refresh token as fallback const creds = await refreshXAIToken(old.access, old.expires, old.refresh);
Defensive patterns
Strategy: validation
Validate before calling
// preflight before refresh: ensure you can always supply a refresh token
if (!stored.refresh) {
throw new Error("No refresh token stored; full device login required instead of refresh");
}
// pass it as the fallback so non-rotating responses still succeed
const creds = await refreshXAIToken(stored.access, stored.expires, stored.refresh); Type guard
function hasRefreshToken(v: unknown, fallback?: string): boolean {
const rt = (typeof v === "object" && v !== null) ? (v as Record<string, unknown>).refresh_token : undefined;
return (typeof rt === "string" && rt !== "") || (typeof fallback === "string" && fallback !== "");
} Try / catch
try {
await xaiProvider.credentials();
} catch (err) {
if (err instanceof AIError.OAuthError && err.message.includes("missing refresh_token")) {
logger.warn("xAI did not return a refresh token; re-running full device login", {});
await xaiProvider.device();
} else {
throw err;
}
} Prevention
- Always thread the previous refresh token into refresh exchanges as the fallback.
- Persist the refresh token durably — losing it forces full re-login when xAI omits rotation.
- Confirm your SuperGrok/xAI account tier issues refresh tokens.
- Log (redacted) token-response keys once to detect refresh_token omission early.
When it happens
Trigger: pollXAIDeviceToken completes with a 200 body whose refresh_token is absent/empty AND no refreshTokenFallback was provided; or the credentials refresh path gets a body with no refresh_token while also having no stored refresh token to fall back on.
Common situations: xAI token endpoint omitting refresh_token on some grant types; first-time login against an endpoint variant that does not rotate refresh tokens; caller not passing the previous refresh token into the refresh exchange.
Related errors
- xAI device-code response missing or invalid required fields.
- ${label} missing access_token
- xAI device-code response was not a JSON object.
- ${label} missing expires_in
- xAI device-code request failed: ${error instanceof Error ? e
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/83a7695ff59d4a3d.
Report an issue: GitHub.