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
- If the body says invalid_grant, the refresh token is dead: trigger a full re-login (startAnthropicLogin + completeAnthropicLogin) and replace the stored credentials.
- 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.
- For 5xx/network errors, retry with backoff; the token itself may still be valid.
- Verify the stored refresh value is the current one and was not truncated or overwritten by a partial credential save.
- 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
- Refresh proactively before expiry so you never refresh with a token that may have been revoked mid-session.
- Persist rotated refresh tokens immediately — they are often single-use; losing the rotation breaks the chain.
- Coordinate refreshes across processes/machines (lock or central refresher) to avoid racing rotations.
- Distinguish invalid_grant (re-auth needed) from 5xx (retry) by inspecting the embedded error text.
- Never log refresh or access tokens when recording the failure body.
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
- Token exchange failed: ${error}
- Missing authorization code
- Invalid authorization state
- Failed to refresh OpenAI Codex token
- xAI token refresh failed: ${response.status}${text ? ` ${tex
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a35ada957091e8af.
Report an issue: GitHub.