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

  1. Parse the status in the message: 400 invalid_grant → perform a fresh device login; 429 → wait and retry; 5xx → retry later.
  2. Re-authenticate the xai provider (full device flow) to get a new refresh token if the old one is revoked/expired.
  3. Verify CLIENT_ID/token-endpoint configuration is unchanged.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/d040d119d6c489e9. Report an issue: GitHub.