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
- Check the caught error (rejected promise/logs) for the provider's error body, e.g. invalid_grant or invalid_client
- Restart the full connect flow to get a fresh authorization code — codes expire quickly and are single-use
- Verify client credentials and that the redirect_uri in the token request matches the one used in the authorize request
- 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
- Never reuse an authorization code — codes are single-use and expire in minutes
- Verify client_id/secret and redirect_uri match between authorize and token requests
- Check proxy/firewall access to the token endpoint before starting the flow
- Retry by restarting the whole connect flow, not just the exchange
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
- Token exchange failed: ${tokenResponse.statusText}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
- Token exchange failed
- Token exchange failed
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/7c4161a81da66747.
Report an issue: GitHub.