mastra-ai/mastra · error

Anthropic token refresh failed: ${error}

Error message

Anthropic token refresh failed: ${error}

What it means

`refreshAnthropicToken` POSTs to Anthropic's token endpoint with grant_type=refresh_token to obtain a new access token. When the response is not ok it throws 'Anthropic token refresh failed: <response body>', embedding the upstream error (typically invalid_grant for a revoked/expired/rotated refresh token). Callers (e.g. anthropicOAuthProvider.refreshToken) surface this whenever stored credentials can no longer be renewed.

Source

Thrown at mastracode/sdk/src/auth/providers/anthropic.ts:145

/**
 * Refresh Anthropic OAuth token
 */
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
  const response = await fetch(TOKEN_URL, {
    method: 'POST',
    signal: AbortSignal.timeout(15_000),
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'refresh_token',
      client_id: CLIENT_ID,
      refresh_token: refreshToken,
    }),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Anthropic token refresh failed: ${error}`);
  }

  const data = (await response.json()) as {
    access_token: string;
    refresh_token: string;
    expires_in: number;
  };

  return {
    refresh: data.refresh_token,
    access: data.access_token,
    expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
  };
}

export const anthropicOAuthProvider: OAuthProviderInterface = {
  id: 'anthropic',
  name: 'Anthropic (Claude Pro/Max)',

View on GitHub (pinned to 75dd419e61)

Solutions

  1. If the body says invalid_grant, the refresh token is dead: trigger a full re-login (startAnthropicLogin + completeAnthropicLogin) and replace the stored credentials.
  2. Check for concurrent refreshes — if refresh tokens rotate on use, ensure only one process refreshes at a time (e.g. lock or single refresher) and persist the rotated token immediately.
  3. For 5xx/network errors, retry with backoff; the token itself may still be valid.
  4. Verify the stored refresh value is the current one and was not truncated or overwritten by a partial credential save.
  5. Inspect the embedded error text — it distinguishes auth problems (re-auth needed) from transient server problems (retry).

Example fix

// before
const creds = await refreshAnthropicToken(stored.refresh); // throws on revoked token, crash loop
// after
let creds;
try {
  creds = await refreshAnthropicToken(stored.refresh);
} catch (e) {
  if (/invalid_grant/i.test(String(e))) {
    creds = await runInteractiveLogin(); // full re-auth; refresh token is unrecoverable
  } else {
    throw e; // transient: retry with backoff
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// skip refresh when the access token is still valid (5-min buffer already baked in)
if (creds.expires > Date.now() + 60_000) {
  return creds; // no refresh needed
}
if (!creds.refresh) throw new Error('No refresh token stored — full re-login required');

Type guard

function isRefreshFailed(e: unknown): e is Error & { message: string } {
  return e instanceof Error && e.message.startsWith('Anthropic token refresh failed:');
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await refreshAnthropicToken(refreshToken);
  } catch (e) {
    if (isRefreshFailed(e) && /invalid_grant/i.test(e.message)) {
      // refresh token revoked/rotated: unrecoverable — force full re-login
      return forceReauthentication();
    }
    if (attempt === 2) throw e; // transient after backoff: give up
    await sleep(2 ** attempt * 500); // 5xx/network: exponential backoff
  }
}

Prevention

When it happens

Trigger: The stored refresh token was revoked (user logged out / reset Claude credentials), already rotated by another client or process (single-use refresh tokens), or expired; Anthropic returns 4xx for a malformed/unknown token; a 5xx or network outage also lands here since any non-ok status throws.

Common situations: Credentials persisted in a database/config then invalidated server-side; two machines (or dev and CI) sharing the same account and racing refreshes, invalidating each other's tokens; clock or storage corruption making a stale token look valid; temporary Anthropic outage causing 5xx on an otherwise valid token.

Related errors


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