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

  1. Read the appended response body — it contains the OAuth error (e.g. invalid_grant, redirect_uri_mismatch) and fix that specific cause.
  2. invalid_grant: restart the whole OAuth flow; authorization codes are single-use and expire in minutes.
  3. redirect_uri_mismatch: ensure the same redirectUri is passed to buildAuthUrl and exchangeToken.
  4. invalid_client: verify config.clientId/clientSecret against current provider settings.
  5. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/6c612668b0793132. Report an issue: GitHub.