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

  1. Close any stale browser tabs from previous authentication attempts and retry `happy` connect so a fresh state is generated and used end-to-end.
  2. If the CLI printed the auth URL, open exactly that URL (fresh copy) rather than a bookmarked or previously-visited one.
  3. Ensure no proxy/extension rewrites or strips the `state` query parameter; try an incognito window or a different browser.
  4. 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

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


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