{"record":{"id":"1ba2a262fee66197","repo":"ruvnet/ruflo","slug":"state-mismatch-the-oauth-callback-did-not-match","errorCode":null,"errorMessage":"state mismatch — the OAuth callback did not match the request this CLI sent","messagePattern":"state mismatch — the OAuth callback did not match the request this CLI sent","errorType":"exception","errorClass":"StateMismatchError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/auth/client.ts","lineNumber":132,"sourceCode":"  }\n}\n\n/** Browser-based loopback PKCE login — the ADR-306 default for an interactive desktop. */\nexport async function browserLogin(print: (line: string) => void): Promise<LoginResult> {\n  const sec = await loadSecurityOAuth();\n  const server = await sec.CallbackServer.bind();\n  const pkce = sec.generatePkce();\n  const url = sec.authorizeUrl(server.redirectUri, pkce.state, pkce.codeChallenge);\n\n  print('Opening your browser to sign in to Cognitum...');\n  print(`If it doesn't open automatically, visit:\\n\\n  ${url}\\n`);\n  await sec.openBrowser(url).catch(() => {}); // best-effort — the URL above is always the fallback\n  print('Waiting for you to finish signing in...');\n\n  const result = await server.awaitCallback();\n  const validated = validateCallback(result.error, result.code, result.state, pkce.state);\n  if (!validated.ok) {\n    if (validated.reason === 'state-mismatch') throw new StateMismatchError();\n    throw new LoginDeniedError(validated.detail ?? 'unknown');\n  }\n\n  const tokens = await sec.exchangeCode(validated.code, pkce.codeVerifier, server.redirectUri);\n  return { tokens, method: 'pkce' };\n}\n\n/** Headless fallback: prints the authorize URL with the OOB redirect, prompts for the pasted code. */\nexport async function manualLogin(\n  print: (line: string) => void,\n  input: NodeJS.ReadableStream = process.stdin,\n): Promise<LoginResult> {\n  const sec = await loadSecurityOAuth();\n  print('Browser-based callback unavailable (SSH/container detected, or --no-browser).\\n');\n\n  const pkce = sec.generatePkce();\n  const url = sec.authorizeUrl(sec.OOB_REDIRECT_URI, pkce.state, pkce.codeChallenge);\n  print(`Open this URL in a browser and authorize:\\n\\n  ${url}\\n`);","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/auth/client.ts#L114-L150","documentation":"Thrown by the interactive PKCE login (StateMismatchError) when the state returned by the OAuth callback does not equal the state the CLI generated and embedded in the authorize URL. This is CSRF protection firing: the callback did not correspond to this CLI's outbound request.","triggerScenarios":"The provider redirects back with a state that differs from pkce.state: a stale authorize tab from a previous login, a second concurrent login, a replayed/intercepted redirect, or a misconfigured redirect_uri.","commonSituations":"User completed an older login tab; two `ruflo auth login` runs in parallel; provider redirect_uri does not match the CLI callback server.","solutions":["Close all stale login tabs/windows and start a single fresh `ruflo auth login`.","Ensure no second process is running a login concurrently for the same profile.","Verify the provider's redirect_uri matches the CLI's loopback callback."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isStateMismatch(e: unknown): boolean {\n  return e instanceof Error && /state mismatch|did not match the request/.test(e.message);\n}","tryCatchPattern":"try {\n  await interactiveLogin(print);\n} catch (e) {\n  if (isStateMismatch(e)) {\n    // restart the login once with a fresh PKCE pair\n  }\n  throw e;\n}","preventionTips":["Do not run concurrent logins for the same profile.","Close prior authorize tabs before starting a new login.","Match the provider redirect_uri to the CLI loopback callback exactly."],"tags":["oauth","pkce","csrf","auth"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}