decolua/9router · error · Error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
Thrown by exchangeClaudeCode when Anthropic's OAuth token endpoint (CLAUDE_CONFIG.tokenUrl) returns a non-2xx status to the authorization_code grant request. The raw response body (Anthropic's OAuth error JSON, e.g. invalid_grant) is appended to the message. The request is sent as JSON (not form-urlencoded) with a PKCE code_verifier and optional `#`-delimited state parsed out of the code.
Source
Thrown at src/lib/oauth/services/claude.js:67
state: codeState || state,
grant_type: "authorization_code",
client_id: CLAUDE_CONFIG.clientId,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
};
const response = await fetch(CLAUDE_CONFIG.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(tokenPayload),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
return await response.json();
}
/**
* Save Claude tokens to server
*/
async saveTokens(tokens) {
const { server, token, userId } = getServerCredentials();
// Server will auto-generate displayName based on existing account count
const response = await fetch(`${server}/api/cli/providers/claude`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-User-Id": userId,View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the appended response body — it names Anthropic's exact OAuth error (usually invalid_grant)
- Re-run the full connect flow to get a fresh authorization code; codes are single-use and short-lived
- Ensure redirect_uri passed to exchangeClaudeCode is byte-identical to the one used in buildClaudeAuthUrl
- Verify the code_verifier belongs to the same session/state as the code (a restarted CLI generates a new verifier)
Example fix
// before: raw body only
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
// after: surface status code too for easier diagnosis
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed (HTTP ${response.status}): ${error}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!code) throw new Error('No authorization code to exchange');
if (!codeVerifier || codeVerifier.length < 43) throw new Error('Invalid PKCE code_verifier — restart the full connect flow');
if (redirectUri !== originalAuthorizeRedirectUri) throw new Error('redirect_uri mismatch between authorize and token steps'); Type guard
function isExchangable(ctx) { return ctx && typeof ctx.code === 'string' && ctx.code.length > 0 && typeof ctx.codeVerifier === 'string' && typeof ctx.redirectUri === 'string'; } Try / catch
try {
const tokens = await service.exchangeClaudeCode(code, redirectUri, codeVerifier, state);
} catch (e) {
if (e.message.startsWith('Token exchange failed')) {
if (e.message.includes('invalid_grant')) {
console.error('Code used/expired — restart connect for a fresh code.');
} else throw e;
} else throw e;
} Prevention
- Never reuse an authorization code — they are single-use and expire in minutes
- Pass the identical redirect_uri used in the authorize step
- Keep code_verifier and code from the same session (don't mix restarted runs)
- Check status.cloude.com / Anthropic status before concluding client misconfig
When it happens
Trigger: Authorization code already used or expired (invalid_grant); code_verifier doesn't match the PKCE challenge; redirect_uri differs from the one used in the authorize step; state mismatch; malformed code string from a mangled callback.
Common situations: Re-running connect with a stale code from a previous attempt (codes are single-use); user taking too long between authorize and exchange; local callback port changed between authorize and token calls; Claude OAuth endpoint outage or client_id rotation.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- xAI token exchange failed: ${error}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/0e646667706019cb.
Report an issue: GitHub.