{"record":{"id":"95a839ac19bbecc9","repo":"NousResearch/hermes-agent","slug":"oauth-popup-was-blocked-allow-popups-for-this-da","errorCode":null,"errorMessage":"OAuth popup was blocked — allow popups for this dashboard and retry","messagePattern":"OAuth popup was blocked — allow popups for this dashboard and retry","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/src/lib/mcp-dashboard-oauth.ts","lineNumber":27,"sourceCode":"  maxPollFailures?: number;\n};\n\nconst defaultSleep = (milliseconds: number) =>\n  new Promise<void>((resolve) => window.setTimeout(resolve, milliseconds));\n\nexport async function completeMcpDashboardOAuth({\n  serverName,\n  start,\n  status,\n  open,\n  sleep = defaultSleep,\n  maxPollFailures = 3,\n}: CompleteOptions): Promise<McpOAuthFlow> {\n  // Open synchronously from the click handler, before the first await. Browsers\n  // otherwise classify the later OAuth popup as unsolicited and block it.\n  const authWindow = open(\"about:blank\", \"_blank\") as Window | null;\n  if (!authWindow) {\n    throw new Error(\"OAuth popup was blocked — allow popups for this dashboard and retry\");\n  }\n  authWindow.opener = null;\n  let started: McpOAuthFlow;\n  try {\n    started = await start(serverName);\n    if (started.status === \"error\") {\n      throw new Error(started.error || \"OAuth failed to start\");\n    }\n    if (!started.authorization_url) {\n      throw new Error(\"OAuth server did not provide an authorization URL\");\n    }\n    authWindow.location.href = started.authorization_url;\n  } catch (error) {\n    authWindow.close();\n    throw error;\n  }\n\n  let pollFailures = 0;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/web/src/lib/mcp-dashboard-oauth.ts#L9-L45","documentation":"completeMcpDashboardOAuth opens an about:blank popup synchronously inside the click handler (browsers block popups opened after an await). If window.open returns null the browser's popup blocker prevented it, and the OAuth flow cannot proceed because there is no window to navigate to the authorization URL.","triggerScenarios":"Calling completeMcpDashboardOAuth outside a direct user-gesture call stack, the browser blocking all popups for the origin, or an embedded webview that does not support window.open.","commonSituations":"Invoking the MCP OAuth button programmatically (from a test or an effect), a browser extension blocking popups, or the dashboard embedded in an iframe/webview with popup denial.","solutions":["Allow popups for the dashboard origin in the browser's site settings, then click the OAuth button again.","Ensure the flow is initiated from a real click event with no await before open() — the code already requires this ordering.","If embedded in a webview/iframe, open the dashboard in a top-level tab."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const probe = window.open('', '_blank')\nif (!probe) {\n  showBanner('Allow popups for this dashboard to connect MCP OAuth servers')\n  probe?.close()\n  return\n}\nprobe.close()\nawait completeMcpDashboardOAuth({ ... })","typeGuard":null,"tryCatchPattern":"try {\n  await completeMcpDashboardOAuth({ serverName, start, status, open })\n} catch (err) {\n  if (String(err).includes('popup was blocked')) {\n    toast('Popups are blocked — enable them for this site, then retry')\n    return\n  }\n  throw err\n}","preventionTips":["Always trigger OAuth from a synchronous click handler (no awaits before window.open).","Probe popup availability up front and tell the user how to unblock.","Never initiate the flow from effects, timers, or tests without stubbing open()."],"tags":["oauth","popup","browser","mcp"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}