slopus/happy · error
Token exchange failed: ${tokenResponse.statusText}
Error message
Token exchange failed: ${tokenResponse.statusText} What it means
Thrown by exchangeCodeForTokens when the OAuth token endpoint responds with a non-OK HTTP status. The CLI sent the authorization code + PKCE verifier to Anthropic's token endpoint, and the server rejected it; only statusText is surfaced, not the response body.
Source
Thrown at packages/happy-cli/src/commands/connect/authenticateClaude.ts:103
): Promise<ClaudeAuthTokens> {
// Exchange code for tokens
const tokenResponse = await fetch(TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'authorization_code',
code: code,
redirect_uri: `http://localhost:${port}/callback`,
client_id: CLIENT_ID,
code_verifier: verifier,
state: state,
}),
});
if (!tokenResponse.ok) {
throw new Error(`Token exchange failed: ${tokenResponse.statusText}`);
}
// {
// token_type: 'Bearer',
// access_token: string,
// expires_in: number,
// refresh_token: string,
// scope: 'user:inference',
// organization: {
// uuid: string,
// name: string
// },
// account: {
// uuid: string,
// email_address: string
// }
// }
const tokenData = await tokenResponse.json() as any;View on GitHub (pinned to b824cd0a46)
Solutions
- Re-run the full `happy connect` / authentication flow to get a fresh authorization code
- Check that the redirect callback URL/port matches the redirect_uri sent in the token request
- Verify CLIENT_ID and token endpoint URL are current for your CLI version
- Capture response body by logging tokenResponse.text() before throwing to see the server's reason
Example fix
// before
if (!tokenResponse.ok) {
throw new Error(`Token exchange failed: ${tokenResponse.statusText}`);
}
// after
if (!tokenResponse.ok) {
const detail = await tokenResponse.text();
throw new Error(`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}: ${detail}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Re-check prerequisites before calling the connect flow
if (!navigator_online()) throw new Error('Network required for token exchange');
// Ensure a fresh auth code: restart the flow instead of reusing a code Try / catch
try {
await authenticateClaude(tokens);
} catch (err) {
if (String(err.message).startsWith('Token exchange failed')) {
// restart full auth flow to obtain a fresh authorization code
await startAuthFlow();
} else throw err;
} Prevention
- Always start a fresh OAuth flow rather than retrying with a consumed code
- Complete the browser approval promptly to avoid code expiry
- Keep the CLI updated for current CLIENT_ID and endpoint config
- Check connectivity/proxy settings before authenticating
When it happens
Trigger: The POST to the token endpoint returns !response.ok — e.g. expired/already-used authorization code, PKCE code_verifier mismatch, wrong client_id, or server outage.
Common situations: User waits too long between auth-start and callback (code expired), retries the flow reusing a consumed code, clock skew invalidating the verifier, or a proxy/firewall mangling the request.
Related errors
- Token exchange failed: ${error}
- Token exchange failed: ${error}
- Token exchange failed
- Authentication failed
- Invalid state parameter
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/d4f222ef37249de4.
Report an issue: GitHub.