decolua/9router · error · Error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
AntigravityService.exchangeCode POSTs grant_type=authorization_code (with client_secret) to the configured tokenUrl and throws this on any non-2xx response, embedding the raw body. It is the standard OAuth2 token-endpoint failure: the authorization code could not be exchanged for tokens.
Source
Thrown at src/lib/oauth/services/antigravity.js:55
async exchangeCode(code, redirectUri) {
const response = await fetch(this.config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: this.config.clientId,
client_secret: this.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();
}
/**
* Get user info from Google
*/
async getUserInfo(accessToken) {
const response = await fetch(`${this.config.userInfoUrl}?alt=json`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
});
if (!response.ok) {
const error = await response.text();View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the embedded OAuth error body (invalid_grant, invalid_client, redirect_uri_mismatch) and act on it
- Restart the full OAuth flow with a fresh code — never retry a used code
- Verify client_id/client_secret and redirect_uri in ANTIGRAVITY_CONFIG exactly match the registered OAuth client
- Confirm the callback redirect_uri string is byte-identical to the one in buildAuthUrl
- If 5xx, wait and retry with a brand-new authorization code
Example fix
// before
const tokens = await svc.exchangeCode(code, redirectUri);
// after
let tokens;
try { tokens = await svc.exchangeCode(code, redirectUri); }
catch (e) {
if (/invalid_grant/.test(e.message)) throw new Error('Code expired or already used — reconnect to restart the flow');
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight before exchanging
if (!code) throw new Error('No authorization code — restart OAuth');
if (!ANTIGRAVITY_CONFIG.clientId || !ANTIGRAVITY_CONFIG.clientSecret) throw new Error('Antigravity OAuth client credentials missing');
if (authRedirectUri !== redirectUri) throw new Error('redirect_uri differs from the authorize request'); Type guard
const isExchangeError = (e) => e instanceof Error && e.message.startsWith('Token exchange failed:');
const oauthErrorCode = (e) => { const m = e.message.match(/"error"\s*:\s*"([^"]+)"/); return m ? m[1] : null; }; Try / catch
try { tokens = await svc.exchangeCode(code, redirectUri); }
catch (e) {
if (!isExchangeError(e)) throw e;
const codeErr = oauthErrorCode(e); // invalid_grant | invalid_client | redirect_uri_mismatch
if (codeErr === 'invalid_grant') startNewOAuthFlow();
else throw new Error(`Antigravity client config problem (${codeErr}) — check clientId/secret/redirect_uri`);
} Prevention
- Treat codes as single-use: one exchange attempt, then a fresh flow on failure
- Keep client_secret/client_id current — Google clients get rotated
- Match redirect_uri exactly (scheme, port, path) between authorize and token calls
- Watch for Google-side endpoint changes in ANTIGRAVITY_CONFIG.tokenUrl
When it happens
Trigger: Code expired/already used (invalid_grant); client_id/client_secret wrong or rotated; redirect_uri mismatch vs the authorize request; Google token endpoint returning 400/401/5xx; clock skew affecting code validity.
Common situations: Retrying after a failed first exchange (code consumed); ANTIGRAVITY_CONFIG credentials outdated; callback served on a different port/path than registered; Google-side outage.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- Failed to load code assist: ${errorText}
- Token exchange failed: ${error}
- Antigravity API error: ${response.status}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/a685b605d2f047b9.
Report an issue: GitHub.