mastra-ai/mastra · error
xAI token refresh failed: ${response.status}${text ? ` ${tex
Error message
xAI token refresh failed: ${response.status}${text ? ` ${text}` : ''} What it means
refreshXAIToken POSTs the stored refresh token to the xAI token endpoint. If the response is not ok, it includes the HTTP status and best-effort body text in this error, so the caller knows exactly why refresh grant was rejected.
Source
Thrown at mastracode/sdk/src/auth/providers/xai.ts:244
}
/**
* Refresh xAI OAuth token
*/
export async function refreshXAIToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials> {
const response = await postForm(
TOKEN_URL,
{
grant_type: 'refresh_token',
client_id: CLIENT_ID,
refresh_token: refreshToken,
},
signal,
);
if (!response.ok) {
const text = await response.text().catch(() => '');
throw new Error(`xAI token refresh failed: ${response.status}${text ? ` ${text}` : ''}`);
}
return credentialsFromTokenResponse((await response.json()) as unknown, refreshToken);
}
export const xaiOAuthProvider: OAuthProviderInterface = {
id: 'xai',
name: 'xAI (Grok)',
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
return loginXAI(callbacks);
},
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
return refreshXAIToken(credentials.refresh);
},
getApiKey(credentials: OAuthCredentials): string {View on GitHub (pinned to 75dd419e61)
Solutions
- Parse the status in the message: 400 invalid_grant → perform a fresh device login; 429 → wait and retry; 5xx → retry later.
- Re-authenticate the xai provider (full device flow) to get a new refresh token if the old one is revoked/expired.
- Verify CLIENT_ID/token-endpoint configuration is unchanged.
- Add backoff before retrying to avoid rate limiting.
Example fix
// before
const creds = await refreshXAIToken(oldRefreshToken); // throws on 400
// after
try {
creds = await refreshXAIToken(oldRefreshToken);
} catch {
creds = await runXAIDeviceLogin(); // invalid_grant → full re-auth
} Defensive patterns
Strategy: try-catch
Validate before calling
// only attempt refresh when a plausible refresh token is stored
if (typeof stored?.refresh !== 'string' || stored.refresh.length === 0) {
await runXaiDeviceLogin();
} Try / catch
try {
creds = await refreshXAIToken(stored.refresh, { signal });
} catch (e) {
const m = e instanceof Error ? e.message : '';
if (m.includes('status: 400') || m.includes('invalid_grant')) {
creds = await runXaiDeviceLogin(); // token dead → full re-auth
} else if (m.includes('status: 429') || /^xAI token refresh failed: 5\d\d/.test(m)) {
await backoffAndRetry();
}
} Prevention
- Refresh before expiry rather than on failure
- Branch on the HTTP status embedded in the message (400 vs 429 vs 5xx)
- Fall back to full device login when the refresh token is revoked/expired
- Persist newly issued refresh tokens immediately
When it happens
Trigger: A credential refresh for the xai provider where the token endpoint responds with response.ok === false (e.g. 400 invalid_grant, 401 invalid_client, 429, 5xx).
Common situations: Refresh token revoked or expired (invalid_grant); client credentials changed; too many refresh attempts triggering 429; xAI outage returning 5xx.
Related errors
- Failed to initiate xAI device authorization: ${response.stat
- Failed to refresh the xAI token.
- Slack OAuth HTTP error: ${tokenResponse.status} ${tokenRespo
- GitHub OAuth token exchange failed: ${res.status}
- Anthropic token refresh failed: ${error}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d040d119d6c489e9.
Report an issue: GitHub.