slopus/happy · error
Invalid state parameter
Error message
Invalid state parameter
What it means
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.
Source
Thrown at packages/happy-cli/src/commands/connect/authenticateCodex.ts:155
/**
* Start local server to handle OAuth callback
*/
async function startCallbackServer(
state: string,
verifier: string,
port: number
): Promise<CodexAuthTokens> {
return new Promise((resolve, reject) => {
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
const url = new URL(req.url!, `http://localhost:${port}`);
if (url.pathname === '/auth/callback') {
const code = url.searchParams.get('code');
const receivedState = url.searchParams.get('state');
if (receivedState !== state) {
res.writeHead(400);
res.end('Invalid state parameter');
server.close();
reject(new Error('Invalid state parameter'));
return;
}
if (!code) {
res.writeHead(400);
res.end('No authorization code received');
server.close();
reject(new Error('No authorization code received'));
return;
}
try {
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code, verifier, port);
View on GitHub (pinned to b824cd0a46)
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.
Example fix
// before: reusing an old authorize URL from a previous run
openBrowser('https://auth.openai.com/oauth/authorize?...&state=aabbccddeeff...') // stale state
// after: always start a fresh flow so state and callback match
const tokens = await authenticateCodex(); // generates new state + callback server Defensive patterns
Strategy: try-catch
Validate before calling
// Before invoking connect, ensure no stale auth tabs/servers and a clean port:
const portInUse = !(await isPortFree(1455));
if (portInUse) console.warn('Close stale auth sessions/tabs or kill the process on port 1455 before reconnecting');
async function isPortFree(port: number): Promise<boolean> {
return new Promise((resolve) => {
const s = require('net').createServer();
s.once('error', () => resolve(false));
s.listen(port, '127.0.0.1', () => s.close(() => resolve(true)));
});
} Try / catch
try {
const tokens = await authenticateCodex();
} catch (err) {
if (err instanceof Error && err.message === 'Invalid state parameter') {
// stale/replayed callback: close old auth tabs, restart the flow once
console.error('OAuth state mismatch — stale callback detected. Rerun connect with a fresh browser tab.');
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- State mismatch. Possible CSRF attack
- Invalid state parameter
- 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/b097ed85e9bc5bfc.
Report an issue: GitHub.