slopus/happy · error

Token exchange failed

Error message

Token exchange failed

What it means

After receiving the authorization code, the callback server exchanges it for tokens at the provider's token endpoint. If that HTTP exchange throws (network error, invalid grant, client authentication failure), the server responds 500 with 'Token exchange failed' and rejects the promise with the underlying error.

Source

Thrown at packages/happy-cli/src/commands/connect/authenticateClaude.ts:175

                    server.close();
                    reject(new Error('No authorization code received'));
                    return;
                }

                try {
                    // Exchange code for tokens
                    const tokens = await exchangeCodeForTokens(code, verifier, port, state);

                    // Redirect to Anthropic's success page
                    res.writeHead(302, {
                        'Location': 'https://console.anthropic.com/oauth/code/success?app=claude-code'
                    });
                    res.end();

                    server.close();
                    resolve(tokens);
                } catch (error) {
                    res.writeHead(500);
                    res.end('Token exchange failed');
                    server.close();
                    reject(error);
                }
            }
        });

        server.listen(port, '127.0.0.1', () => {
            // console.log(`🔐 OAuth callback server listening on port ${port}`);
        });

        // Timeout after 5 minutes
        setTimeout(() => {
            server.close();
            reject(new Error('Authentication timeout'));
        }, 5 * 60 * 1000);
    });
}

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Check the caught error (rejected promise/logs) for the provider's error body, e.g. invalid_grant or invalid_client
  2. Restart the full connect flow to get a fresh authorization code — codes expire quickly and are single-use
  3. Verify client credentials and that the redirect_uri in the token request matches the one used in the authorize request
  4. Check network/proxy connectivity to the token endpoint

Example fix

// before
} catch (error) {
    res.writeHead(500);
    res.end('Token exchange failed');
// after
} catch (error) {
    res.writeHead(500);
    res.end(`Token exchange failed: ${error instanceof Error ? error.message : error}`);
    reject(error instanceof Error ? error : new Error(String(error)));
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.ANTHROPIC_CLIENT_ID || !process.env.ANTHROPIC_CLIENT_SECRET) {
  throw new Error('OAuth client credentials missing; token exchange would fail');
}
if (!navigator.onLine) throw new Error('Network offline; token exchange would fail');

Try / catch

try {
  const tokens = await authenticateClaude();
} catch (e) {
  if (String(e).includes('Token exchange failed')) {
    console.error('Token endpoint rejected the exchange; get a fresh code (codes are single-use) and check credentials/network');
  } else throw e;
}

Prevention

When it happens

Trigger: The fetch/POST to the token endpoint fails or returns an error status: expired or already-used authorization code, wrong client_id/client_secret, redirect_uri mismatch with the token request, or network/connectivity failure.

Common situations: Authorization codes are single-use and short-lived — retries or duplicate callbacks cause invalid_grant; OAuth app credentials misconfigured; corporate proxy blocking the token endpoint; clock skew affecting token validation.

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/7c4161a81da66747. Report an issue: GitHub.