{"record":{"id":"b097ed85e9bc5bfc","repo":"slopus/happy","slug":"invalid-state-parameter-b097ed","errorCode":null,"errorMessage":"Invalid state parameter","messagePattern":"Invalid state parameter","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/happy-cli/src/commands/connect/authenticateCodex.ts","lineNumber":155,"sourceCode":"\n/**\n * Start local server to handle OAuth callback\n */\nasync function startCallbackServer(\n    state: string,\n    verifier: string,\n    port: number\n): Promise<CodexAuthTokens> {\n    return new Promise((resolve, reject) => {\n        const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n            const url = new URL(req.url!, `http://localhost:${port}`);\n\n            if (url.pathname === '/auth/callback') {\n                const code = url.searchParams.get('code');\n                const receivedState = url.searchParams.get('state');\n\n                if (receivedState !== state) {\n                    res.writeHead(400);\n                    res.end('Invalid state parameter');\n                    server.close();\n                    reject(new Error('Invalid state parameter'));\n                    return;\n                }\n\n                if (!code) {\n                    res.writeHead(400);\n                    res.end('No authorization code received');\n                    server.close();\n                    reject(new Error('No authorization code received'));\n                    return;\n                }\n\n                try {\n                    // Exchange code for tokens\n                    const tokens = await exchangeCodeForTokens(code, verifier, port);\n","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/commands/connect/authenticateCodex.ts#L137-L173","documentation":"During the Codex (OpenAI) OAuth flow, happy-cli starts a local HTTP callback server and generates a random `state` value that is embedded in the authorization URL. When Google/OpenAI redirects the browser back to `/auth/callback`, the server compares the `state` query parameter it receives against the value it generated. If they differ, it rejects the authentication with 'Invalid state parameter' to prevent CSRF and session-fixation attacks on the OAuth flow.","triggerScenarios":"The browser (or anything else) hits http://localhost:<port>/auth/callback with a `state` query param that does not byte-equal the state generated at the start of this `authenticateCodex()` run — e.g. the callback URL was copied from a previous auth attempt, the redirect was replayed/bookmarked from an old session, or the state param was stripped/mangled by a proxy or URL shortener.","commonSituations":"Re-running `happy` connect while an old auth tab is still open, so the stale tab completes the callback with its original (now-dead) state; corporate proxies or browser extensions rewriting query strings; a user manually pasting an authorization URL from one machine into another's callback server.","solutions":["Close any stale browser tabs from previous authentication attempts and retry `happy` connect so a fresh state is generated and used end-to-end.","If the CLI printed the auth URL, open exactly that URL (fresh copy) rather than a bookmarked or previously-visited one.","Ensure no proxy/extension rewrites or strips the `state` query parameter; try an incognito window or a different browser.","If it persists, clear the CLI's cached Codex credentials (if any) and rerun the connect flow; check the CLI is not running two concurrent authenticateCodex() calls that race on the same port."],"exampleFix":"// before: reusing an old authorize URL from a previous run\nopenBrowser('https://auth.openai.com/oauth/authorize?...&state=aabbccddeeff...')  // stale state\n// after: always start a fresh flow so state and callback match\nconst tokens = await authenticateCodex(); // generates new state + callback server","handlingStrategy":"try-catch","validationCode":"// Before invoking connect, ensure no stale auth tabs/servers and a clean port:\nconst portInUse = !(await isPortFree(1455));\nif (portInUse) console.warn('Close stale auth sessions/tabs or kill the process on port 1455 before reconnecting');\n\nasync function isPortFree(port: number): Promise<boolean> {\n  return new Promise((resolve) => {\n    const s = require('net').createServer();\n    s.once('error', () => resolve(false));\n    s.listen(port, '127.0.0.1', () => s.close(() => resolve(true)));\n  });\n}","typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await authenticateCodex();\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid state parameter') {\n    // stale/replayed callback: close old auth tabs, restart the flow once\n    console.error('OAuth state mismatch — stale callback detected. Rerun connect with a fresh browser tab.');\n  } else throw err;\n}","preventionTips":["Always start a fresh connect flow; never reuse or bookmark authorize/callback URLs from previous attempts.","Close old OAuth browser tabs before re-running connect.","Run only one authentication flow at a time — concurrent runs race on the callback port and state.","Avoid proxies/extensions that rewrite localhost callback query strings; use an incognito window if needed."],"tags":["oauth","csrf","state-mismatch","authentication"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}