decolua/9router · error · Error
`Token exchange failed: ${error}`
Error message
`Token exchange failed: ${error}` What it means
claude.js exchangeToken performs the PKCE authorization-code exchange with Anthropic's token endpoint; any non-ok response triggers this error carrying the raw response body. Common OAuth failure codes (invalid_grant, invalid_client, redirect_uri_mismatch) plus state/verifier mismatches surface here.
Source
Thrown at src/lib/oauth/providers/claude.js:47
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
code: authCode,
state: codeState || state,
grant_type: "authorization_code",
client_id: config.clientId,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
return await response.json();
},
mapTokens: (tokens) => ({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
scope: tokens.scope,
}),
};
export default claude;
View on GitHub (pinned to 90b52e06ff)
Solutions
- Inspect the appended body for the OAuth error code and address it directly (invalid_grant -> restart flow).
- invalid_grant: codes are single-use and short-lived; restart the login from buildAuthUrl.
- Ensure the same codeVerifier generated for the auth URL is passed to exchangeToken (don't regenerate between steps).
- Match redirect_uri exactly between buildAuthUrl and exchangeToken, including port and scheme.
- If 5xx, retry the full flow after a short wait.
Example fix
// before: fresh verifier at exchange time kills PKCE const verifier = generateVerifier(); // different from the one in the auth URL await exchangeToken(config, code, redirectUri, verifier, state); // after: persist the original verifier await exchangeToken(config, code, redirectUri, pkce.verifier, state);
Defensive patterns
Strategy: try-catch
Validate before calling
if (!code || !codeVerifier || !redirectUri) throw new Error('PKCE exchange requires code, codeVerifier and redirectUri');
// code may carry '#state' — that is handled internally, do not pre-strip differently Type guard
function isPkceExchangeInput(v) {
return typeof v?.code === 'string' && v.code.length > 0 &&
typeof v?.codeVerifier === 'string' && v.codeVerifier.length >= 43 &&
typeof v?.redirectUri === 'string' && /^https?:\/\//.test(v.redirectUri);
} Try / catch
try {
const tokens = await claude.exchangeToken(config, code, redirectUri, pkce.verifier, state);
// use tokens
} catch (err) {
if (String(err.message).startsWith('Token exchange failed:')) {
if (/invalid_grant/.test(err.message)) restartLogin(); // replayed/expired code or verifier mismatch
else if (/invalid_client/.test(err.message)) updateClaudeConfig();
else if (/redirect_uri_mismatch/.test(err.message)) alignRedirectUri();
else backoffAndRetry();
} else throw err;
} Prevention
- Persist the exact PKCE verifier produced for the auth URL and pass that same instance to exchangeToken.
- Reuse one redirectUri object/value across buildAuthUrl and exchangeToken.
- Never run two OAuth flows in parallel tabs for the same account.
- Keep CLAUDE_CONFIG.clientId current with Anthropic's published OAuth client values.
- On failure, read the embedded response body for the OAuth error code before retrying.
When it happens
Trigger: Exchanging a code whose PKCE code_verifier doesn't match the challenge used in buildAuthUrl, replaying a consumed/expired code, passing the code including the '#state' suffix (it is split, but a state mismatch still fails server-side), wrong clientId in CLAUDE_CONFIG, or redirect_uri differing between authorize and exchange.
Common situations: Anthropic rotating its OAuth client config, users completing the flow twice in two tabs, code pasted from a browser where the fragment was truncated, clock skew expiring the code, or CI environments where the callback port differs from the auth URL.
Related errors
- `Token exchange failed: ${error}`
- `Cline token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- xAI token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/3f72ea3c163ac3b8.
Report an issue: GitHub.