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

  1. 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.
  2. Never refresh or bookmark the callback URL — a second GET reuses the consumed code; start a new authentication attempt.
  3. Check network reachability to auth.openai.com (VPN, corporate proxy, firewall) and system clock accuracy.
  4. 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

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


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