decolua/9router · error · Error
`Token exchange failed: ${error}`
Error message
`Token exchange failed: ${error}` What it means
antigravity.js exchangeToken POSTs the authorization code to the provider token endpoint and, when the HTTP response status is not ok, throws this error with the raw response body appended. It means the upstream OAuth server refused the authorization-code exchange (invalid code, redirect_uri mismatch, expired code, bad client credentials, etc.).
Source
Thrown at src/lib/oauth/providers/antigravity.js:36
exchangeToken: async (config, code, redirectUri) => {
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: config.clientId,
client_secret: config.clientSecret,
code: code,
redirect_uri: redirectUri,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
return await response.json();
},
postExchange: async (tokens) => {
const loadHeaders = {
"Authorization": `Bearer ${tokens.access_token}`,
"Content-Type": "application/json",
"User-Agent": ANTIGRAVITY_CONFIG.loadCodeAssistUserAgent,
"x-request-source": "local",
};
const metadata = getOAuthClientMetadata();
// Fetch user info
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
"x-request-source": "local",View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the appended response body — it contains the OAuth error (e.g. invalid_grant, redirect_uri_mismatch) and fix that specific cause.
- invalid_grant: restart the whole OAuth flow; authorization codes are single-use and expire in minutes.
- redirect_uri_mismatch: ensure the same redirectUri is passed to buildAuthUrl and exchangeToken.
- invalid_client: verify config.clientId/clientSecret against current provider settings.
- Check upstream status page / retry if the body indicates a 5xx outage.
Example fix
// before: mismatched redirect_uri between steps buildAuthUrl(config, 'http://localhost:20128/callback', state) exchangeToken(config, code, 'http://localhost:3000/callback') // 400 redirect_uri_mismatch // after: reuse the identical redirect URI const redirectUri = 'http://localhost:20128/callback'; buildAuthUrl(config, redirectUri, state); await exchangeToken(config, code, redirectUri);
Defensive patterns
Strategy: try-catch
Validate before calling
const code = new URL(callbackUrl).searchParams.get('code');
if (!code) throw new Error('callback URL missing code param — abort before exchanging');
if (exchangedCodes.has(code)) throw new Error('authorization code already consumed'); Type guard
function hasValidCodeParams(params) {
return typeof params?.code === 'string' && params.code.length > 0 && typeof params?.redirectUri === 'string' && params.redirectUri.length > 0;
} Try / catch
try {
const tokens = await antigravity.exchangeToken(config, code, redirectUri);
// use tokens
} catch (err) {
if (String(err.message).startsWith('Token exchange failed:')) {
const body = err.message.slice('Token exchange failed:'.length);
if (/invalid_grant/.test(body)) startNewAuthFlow(); // code expired/used
else if (/redirect_uri_mismatch/.test(body)) fixRedirectUri();
else if (/invalid_client/.test(body)) checkClientSecret();
else retryWithBackoff(); // 5xx
} else throw err;
} Prevention
- Use a single shared redirectUri constant for both buildAuthUrl and exchangeToken.
- Mark authorization codes as consumed to block callback-page refreshes from replaying them.
- Exchange codes immediately after the callback; they expire within minutes.
- Parse the appended response body (it contains the OAuth error code) before deciding to retry.
- Keep ANTIGRAVITY_CONFIG client credentials in sync with provider settings.
When it happens
Trigger: Calling exchangeToken after the browser callback with: an authorization code already consumed or expired, a redirect_uri different from the one used in buildAuthUrl, wrong/missing client_secret in ANTIGRAVITY_CONFIG, or a 4xx/5xx from the token endpoint (rate limit, upstream outage).
Common situations: User refreshing the callback page (code replay), starting the OAuth flow behind a different port/redirect than configured, stale deployed config after provider rotated its client secret, or network middleboxes returning HTML error pages.
Related errors
- `Token exchange failed: ${error}`
- `Cline token exchange failed: ${error}`
- `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/6c612668b0793132.
Report an issue: GitHub.