slopus/happy · error
Token exchange failed
Error message
Token exchange failed
What it means
After receiving a valid `code`, startCallbackServer calls exchangeCodeForTokens(), which POSTs to OpenAI's token endpoint with the code, PKCE verifier, client_id, and redirect_uri. Any failure inside that block — non-2xx token response, network error, or a malformed/unparseable ID token — is caught and the callback responds HTTP 500 'Token exchange failed' while rejecting the promise with the underlying error.
Source
Thrown at packages/happy-cli/src/commands/connect/authenticateCodex.ts:189
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code, verifier, port);
// Send success response to browser
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<html>
<body style="font-family: sans-serif; padding: 20px;">
<h2>✅ Authentication Successful!</h2>
<p>You can close this window and return to your terminal.</p>
<script>setTimeout(() => window.close(), 3000);</script>
</body>
</html>
`);
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
- Read the CLI log / the rejected error for the detailed token-endpoint response body (e.g. 'invalid_grant'), then rerun the connect flow with a fresh authorization code.
- Never refresh or bookmark the callback URL — a second GET reuses the consumed code; start a new authentication attempt.
- Check network reachability to auth.openai.com (VPN, corporate proxy, firewall) and system clock accuracy.
- Update happy-cli; older builds can mis-parse ID token claims (chatgpt_account_id extraction) and fail after a successful exchange.
Example fix
// before: double-consuming the code by reloading the callback page // GET http://localhost:1455/auth/callback?code=abc&state=xyz (x2 — second fails with invalid_grant) // after: on any failure, rerun the full flow const tokens = await authenticateCodex(); // new code, new verifier, new state
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm the token endpoint is reachable before starting the flow
const reachable = await fetch('https://auth.openai.com/.well-known/openid-configuration')
.then(r => r.ok).catch(() => false);
if (!reachable) throw new Error('auth.openai.com unreachable — fix network/proxy before authenticating'); Try / catch
async function connectWithRetry(maxAttempts = 2): Promise<CodexAuthTokens> {
let lastErr: unknown;
for (let i = 0; i < maxAttempts; i++) {
try {
return await authenticateCodex();
} catch (err) {
lastErr = err;
const msg = err instanceof Error ? err.message : String(err);
if (/Token exchange failed|invalid_grant/i.test(msg)) continue; // fresh code, new flow
throw err;
}
}
throw lastErr;
} Prevention
- Never refresh or re-open the callback page — the authorization code is consumed on first exchange.
- Verify network access to auth.openai.com (VPN, corporate proxy, firewall) before connecting.
- Keep system clock accurate (NTP); skew can invalidate tokens and codes.
- Keep happy-cli updated so JWT parsing and token-claim extraction match the provider's current ID token format.
When it happens
Trigger: The POST to https://auth.openai.com/oauth/token fails: invalid/expired/already-used authorization code, PKCE code_verifier mismatch (redirect_uri or port changed between authorize and token calls), network outage, or the returned id_token is not a valid 3-part JWT (parseJWT throws).
Common situations: Authorization code replayed after a browser refresh of the callback page; corporate firewall/proxy blocking auth.openai.com; system clock skew invalidating tokens; OpenAI-side outage returning 5xx; port reuse causing a different redirect_uri than the one used in the authorize request.
Related errors
- Token exchange failed
- Token exchange failed
- Token exchange failed: ${tokenResponse.statusText}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/07e97cfc169be058.
Report an issue: GitHub.