slopus/happy · error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
Thrown by exchangeCodeForTokens for the Codex (OpenAI) OAuth flow when the token endpoint responds non-OK. Unlike the Claude variant, it includes the response body text in the message, so the server's error description is visible.
Source
Thrown at packages/happy-cli/src/commands/connect/authenticateCodex.ts:111
port: number
): Promise<CodexAuthTokens> {
const response = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
code: code,
code_verifier: verifier,
redirect_uri: `http://localhost:${port}/auth/callback`,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
const data = (await response.json() as any);
// Parse ID token to get account ID
const idTokenPayload = parseJWT(data.id_token);
// The account ID is stored at chatgpt_account_id in the payload
let accountId = idTokenPayload.chatgpt_account_id;
// Check nested location
if (!accountId) {
const authClaim = idTokenPayload['https://api.openai.com/auth'];
if (authClaim && typeof authClaim === 'object') {
accountId = authClaim.chatgpt_account_id || authClaim.account_id;
}
}
View on GitHub (pinned to b824cd0a46)
Solutions
- Read the error body in the message and follow the provider's documented cause (e.g. invalid_grant)
- Re-run the Codex authentication flow for a fresh authorization code
- Ensure the callback port matches the redirect_uri exactly
- Update the CLI if the provider changed token-endpoint requirements
Example fix
// before
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
// after
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed (HTTP ${response.status}): ${error}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the callback port matches the redirect_uri before exchanging
if (!redirectUri.startsWith(`http://localhost:${port}/auth/callback`)) {
throw new Error('redirect_uri/port mismatch');
} Try / catch
try {
await exchangeCodeForTokens(code, port);
} catch (err) {
if (String(err.message).startsWith('Token exchange failed')) {
console.error('Codex token exchange rejected:', err.message);
// restart auth to get a fresh code
await runCodexAuth();
} else throw err;
} Prevention
- Complete auth promptly; codes expire quickly
- Never reuse an authorization code across attempts
- Keep callback port consistent with redirect_uri
- Check provider status page / proxy interference on repeated failures
When it happens
Trigger: POST to the OpenAI token endpoint with authorization_code, code_verifier, and redirect_uri http://localhost:<port>/auth/callback returns !response.ok — expired/replayed code, PKCE mismatch, client misconfiguration, or 5xx.
Common situations: Callback server bound to a different port than redirect_uri, user retried an already-consumed code, corporate proxy intercepting localhost callback or outbound call, provider outage.
Related errors
- Token exchange failed: ${tokenResponse.statusText}
- 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/69555ae46c27bf19.
Report an issue: GitHub.