coleam00/Archon · error
OAuth state mismatch.
Error message
OAuth state mismatch.
What it means
Thrown by runOpenAiManualLogin when the user pastes an OpenAI OAuth authorization response whose `state` parameter is present but does not match the `state` issued for the in-flight PKCE flow. The state parameter is a CSRF protection binding the pasted callback to this login session; a mismatch means the code belongs to a different/older login attempt.
Source
Thrown at packages/core/src/credentials/oauth-bridge.ts:219
/**
* The Archon-owned ChatGPT/Codex manual login (#1924): build the authorize
* URL (PKCE), surface it on the session, wait for the pasted redirect URL /
* code via the same `codeDeferred` the Pi manual flows use (so poll(code) and
* abort semantics are identical), then exchange it directly — capturing the
* `id_token` Pi drops. Runs NO local callback server (the #1963 wedge
* pattern); the user pastes the final redirect URL or code back instead.
*/
async function runOpenAiManualLogin(session: OAuthSession): Promise<OpenAiOAuthCredentials> {
const flow = createOpenAiAuthorizeFlow();
session.url = flow.url;
if (session.mode === 'pending') session.mode = 'manual';
session.firstSignal.resolve(true);
// Rejected by abortSession on cancel/supersede/expiry — same as Pi flows.
const input = await session.codeDeferred.promise;
const parsed = parseOpenAiAuthorizationInput(input);
if (parsed.state && parsed.state !== flow.state) {
throw new Error('OAuth state mismatch.');
}
if (!parsed.code) {
throw new Error('Missing authorization code.');
}
// Returns its true type — the loginPromise join is typed as delivery.ts's
// loose `OAuthCredentials`, which this satisfies structurally (no cast).
return exchangeOpenAiAuthorizationCode(parsed.code, flow.verifier, session.abort.signal);
}
/**
* Begin a subscription login for a vendor (anthropic/openai/github-copilot;
* legacy claude/codex/copilot ids accepted). Kicks off the held login —
* Pi's `login()` for anthropic/github-copilot, the Archon-owned PKCE flow for
* openai — and returns once the first signal has populated the URL (manual)
* or user-code (device), or a short timeout elapses.
*/
export async function startOAuth(userId: string, providerId: string): Promise<StartOAuthResult> {
// Expired sessions may also hold a callback server — include them in theView on GitHub (pinned to 0773b97458)
Solutions
- Complete the login again and paste the authorization response from that exact attempt (same state value).
- Cancel any other concurrent OpenAI logins so only one flow is live, then retry.
- Copy the full redirect URL from the browser tab the current login opened — not an older one.
- If the state keeps mismatching, restart the login to mint a fresh flow and state.
Example fix
// before: pasting stale URL from a cancelled attempt https://auth.openai.com/oauth/callback?state=OLD_STATE&code=... // after: fresh response from the current login attempt https://auth.openai.com/oauth/callback?state=CURRENT_STATE&code=...
Defensive patterns
Strategy: retry
Validate before calling
import { parseOpenAiAuthorizationInput } from './openai-oauth';
function inputMatchesFlow(input: string, flow: { state: string }): boolean {
const parsed = parseOpenAiAuthorizationInput(input);
return !parsed.state || parsed.state === flow.state;
} Try / catch
try {
const creds = await loginPromise;
} catch (e) {
if ((e as Error).message === 'OAuth state mismatch.') {
// restart the login flow and paste the response from the NEW attempt
} else throw e;
} Prevention
- Always paste the redirect URL from the browser tab the current login opened.
- Cancel/supersede stale login attempts before starting a new one.
- Run one OpenAI login at a time per install to avoid mixing flows.
- If unsure, restart the login — state is per-attempt and never reusable.
When it happens
Trigger: During manual OpenAI subscription login, supplying a code via the deferred prompt (session.codeDeferred) where parseOpenAiAuthorizationInput extracts a state that differs from flow.state — pasting a callback URL from a previous login attempt, a different browser session, or a stale/stopped flow.
Common situations: Re-pasting an old authorization URL after the login was cancelled and restarted; running two logins concurrently and mixing up their callback URLs; copying the redirect from a different Archon instance.
Related errors
- Missing authorization code.
- OpenAI token ${operation} request timed out.
- OpenAI token ${operation} request failed: ${error instanceof
- OpenAI token ${operation} failed (${response.status})${error
- OpenAI token ${operation} returned a non-JSON response (HTTP
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/e783878b747c1c9b.
Report an issue: GitHub.