mastra-ai/mastra · error

xAI token response missing refresh_token

Error message

xAI token response missing refresh_token

What it means

credentialsFromTokenResponse requires a refresh token: it accepts record.refresh_token if present, otherwise falls back to previousRefreshToken (xAI may not rotate it). If neither exists, credentials could never be renewed, so this error is thrown.

Source

Thrown at mastracode/sdk/src/auth/providers/xai.ts:63

    throw new Error(`xAI device authorization returned a non-https verification_uri: ${raw}`);
  }
  return parsed.toString();
}

function credentialsFromTokenResponse(data: unknown, previousRefreshToken?: string): OAuthCredentials {
  const record = (data ?? {}) as Record<string, unknown>;
  const access = record.access_token;
  if (typeof access !== 'string' || access.length === 0) {
    throw new Error('xAI token response missing access_token');
  }

  // xAI may not rotate the refresh token on refresh; keep the previous one.
  const refresh =
    typeof record.refresh_token === 'string' && record.refresh_token.length > 0
      ? record.refresh_token
      : previousRefreshToken;
  if (!refresh) {
    throw new Error('xAI token response missing refresh_token');
  }

  const expiresIn =
    typeof record.expires_in === 'number' && record.expires_in > 0
      ? record.expires_in
      : DEFAULT_TOKEN_EXPIRES_IN_SECONDS;

  return {
    access,
    refresh,
    expires: Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS,
  };
}

/**
 * Serializable pending state for an xAI device-code login. Safe to persist
 * (e.g. a `pending jsonb` column) so polling can span HTTP requests.
 */

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the previous refresh token is passed to refreshXAIToken so the fallback works when xAI does not rotate.
  2. Log the token response to confirm whether refresh_token was omitted by the provider.
  3. Re-run the device login to obtain a grant that includes a refresh token.
  4. Check xAI API docs/changelog for refresh-token issuance changes and upgrade the SDK if needed.

Example fix

// before
return credentialsFromTokenResponse(json); // previous refresh token lost
// after
return credentialsFromTokenResponse(json, storedRefreshToken); // keeps previous token if not rotated
Defensive patterns

Strategy: validation

Validate before calling

// before refreshing, ensure you still hold the previous refresh token to pass through
if (!previousRefreshToken && !responseIncludesRefreshToken) {
  await runFullXaiDeviceLogin(); // refresh chain broken; re-auth
}

Type guard

function hasRecoverableRefreshToken(data: unknown, previous?: string): boolean {
  const r = (data ?? {}) as Record<string, unknown>;
  const next = r.refresh_token;
  return (typeof next === 'string' && next.length > 0) || (typeof previous === 'string' && previous.length > 0);
}

Try / catch

try {
  creds = await refreshXAIToken(stored.refresh);
} catch (e) {
  if (e instanceof Error && e.message.includes('missing refresh_token')) {
    creds = await runXaiDeviceLogin(); // no renewable token; full login
  }
}

Prevention

When it happens

Trigger: pollXAITokenOnce or refreshXAIToken gets a response with a valid access_token but no refresh_token, while previousRefreshToken is undefined (initial device login) or also empty.

Common situations: xAI changes token-rotation behavior and omits refresh_token on an initial grant; the caller dropped the previous refresh token before calling refresh; scope/plan differences where the provider does not issue refresh tokens.

Related errors


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