{"record":{"id":"07e97cfc169be058","repo":"slopus/happy","slug":"token-exchange-failed-07e97c","errorCode":null,"errorMessage":"Token exchange failed","messagePattern":"Token exchange failed","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"packages/happy-cli/src/commands/connect/authenticateCodex.ts","lineNumber":189,"sourceCode":"                    // Exchange code for tokens\n                    const tokens = await exchangeCodeForTokens(code, verifier, port);\n\n                    // Send success response to browser\n                    res.writeHead(200, { 'Content-Type': 'text/html' });\n                    res.end(`\n                        <html>\n                        <body style=\"font-family: sans-serif; padding: 20px;\">\n                            <h2>✅ Authentication Successful!</h2>\n                            <p>You can close this window and return to your terminal.</p>\n                            <script>setTimeout(() => window.close(), 3000);</script>\n                        </body>\n                        </html>\n                    `);\n\n                    server.close();\n                    resolve(tokens);\n                } catch (error) {\n                    res.writeHead(500);\n                    res.end('Token exchange failed');\n                    server.close();\n                    reject(error);\n                }\n            }\n        });\n\n        server.listen(port, '127.0.0.1', () => {\n            // console.log(`🔐 OAuth callback server listening on port ${port}`);\n        });\n\n        // Timeout after 5 minutes\n        setTimeout(() => {\n            server.close();\n            reject(new Error('Authentication timeout'));\n        }, 5 * 60 * 1000);\n    });\n}","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/commands/connect/authenticateCodex.ts#L171-L207","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: double-consuming the code by reloading the callback page\n// GET http://localhost:1455/auth/callback?code=abc&state=xyz  (x2 — second fails with invalid_grant)\n// after: on any failure, rerun the full flow\nconst tokens = await authenticateCodex(); // new code, new verifier, new state","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the token endpoint is reachable before starting the flow\nconst reachable = await fetch('https://auth.openai.com/.well-known/openid-configuration')\n  .then(r => r.ok).catch(() => false);\nif (!reachable) throw new Error('auth.openai.com unreachable — fix network/proxy before authenticating');","typeGuard":null,"tryCatchPattern":"async function connectWithRetry(maxAttempts = 2): Promise<CodexAuthTokens> {\n  let lastErr: unknown;\n  for (let i = 0; i < maxAttempts; i++) {\n    try {\n      return await authenticateCodex();\n    } catch (err) {\n      lastErr = err;\n      const msg = err instanceof Error ? err.message : String(err);\n      if (/Token exchange failed|invalid_grant/i.test(msg)) continue; // fresh code, new flow\n      throw err;\n    }\n  }\n  throw lastErr;\n}","preventionTips":["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."],"tags":["oauth","token-exchange","network","pkce"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}