{"record":{"id":"d040d119d6c489e9","repo":"mastra-ai/mastra","slug":"xai-token-refresh-failed-response-status-text","errorCode":null,"errorMessage":"xAI token refresh failed: ${response.status}${text ? ` ${text}` : ''}","messagePattern":"xAI token refresh failed: (.+?)(.+?)` : ''\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/auth/providers/xai.ts","lineNumber":244,"sourceCode":"}\n\n/**\n * Refresh xAI OAuth token\n */\nexport async function refreshXAIToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials> {\n  const response = await postForm(\n    TOKEN_URL,\n    {\n      grant_type: 'refresh_token',\n      client_id: CLIENT_ID,\n      refresh_token: refreshToken,\n    },\n    signal,\n  );\n\n  if (!response.ok) {\n    const text = await response.text().catch(() => '');\n    throw new Error(`xAI token refresh failed: ${response.status}${text ? ` ${text}` : ''}`);\n  }\n\n  return credentialsFromTokenResponse((await response.json()) as unknown, refreshToken);\n}\n\nexport const xaiOAuthProvider: OAuthProviderInterface = {\n  id: 'xai',\n  name: 'xAI (Grok)',\n\n  async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n    return loginXAI(callbacks);\n  },\n\n  async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n    return refreshXAIToken(credentials.refresh);\n  },\n\n  getApiKey(credentials: OAuthCredentials): string {","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/auth/providers/xai.ts#L226-L262","documentation":"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.","triggerScenarios":"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).","commonSituations":"Refresh token revoked or expired (invalid_grant); client credentials changed; too many refresh attempts triggering 429; xAI outage returning 5xx.","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."],"exampleFix":"// before\nconst creds = await refreshXAIToken(oldRefreshToken); // throws on 400\n// after\ntry {\n  creds = await refreshXAIToken(oldRefreshToken);\n} catch {\n  creds = await runXAIDeviceLogin(); // invalid_grant → full re-auth\n}","handlingStrategy":"try-catch","validationCode":"// only attempt refresh when a plausible refresh token is stored\nif (typeof stored?.refresh !== 'string' || stored.refresh.length === 0) {\n  await runXaiDeviceLogin();\n}","typeGuard":null,"tryCatchPattern":"try {\n  creds = await refreshXAIToken(stored.refresh, { signal });\n} catch (e) {\n  const m = e instanceof Error ? e.message : '';\n  if (m.includes('status: 400') || m.includes('invalid_grant')) {\n    creds = await runXaiDeviceLogin(); // token dead → full re-auth\n  } else if (m.includes('status: 429') || /^xAI token refresh failed: 5\\d\\d/.test(m)) {\n    await backoffAndRetry();\n  }\n}","preventionTips":["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"],"tags":["oauth","xai","token-refresh","http"],"backgroundTag":"oauth-refresh-request-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}