NousResearch/hermes-agent · error
OAuth popup was blocked — allow popups for this dashboard an
Error message
OAuth popup was blocked — allow popups for this dashboard and retry
What it means
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.
Source
Thrown at web/src/lib/mcp-dashboard-oauth.ts:27
maxPollFailures?: number;
};
const defaultSleep = (milliseconds: number) =>
new Promise<void>((resolve) => window.setTimeout(resolve, milliseconds));
export async function completeMcpDashboardOAuth({
serverName,
start,
status,
open,
sleep = defaultSleep,
maxPollFailures = 3,
}: CompleteOptions): Promise<McpOAuthFlow> {
// Open synchronously from the click handler, before the first await. Browsers
// otherwise classify the later OAuth popup as unsolicited and block it.
const authWindow = open("about:blank", "_blank") as Window | null;
if (!authWindow) {
throw new Error("OAuth popup was blocked — allow popups for this dashboard and retry");
}
authWindow.opener = null;
let started: McpOAuthFlow;
try {
started = await start(serverName);
if (started.status === "error") {
throw new Error(started.error || "OAuth failed to start");
}
if (!started.authorization_url) {
throw new Error("OAuth server did not provide an authorization URL");
}
authWindow.location.href = started.authorization_url;
} catch (error) {
authWindow.close();
throw error;
}
let pollFailures = 0;View on GitHub (pinned to c896c09c42)
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.
Defensive patterns
Strategy: validation
Validate before calling
const probe = window.open('', '_blank')
if (!probe) {
showBanner('Allow popups for this dashboard to connect MCP OAuth servers')
probe?.close()
return
}
probe.close()
await completeMcpDashboardOAuth({ ... }) Try / catch
try {
await completeMcpDashboardOAuth({ serverName, start, status, open })
} catch (err) {
if (String(err).includes('popup was blocked')) {
toast('Popups are blocked — enable them for this site, then retry')
return
}
throw err
} Prevention
- 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().
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OAuth authorization window was closed before completion
- OAuth failed to start
- OAuth server did not provide an authorization URL
- OAuth authorization failed
- File uploads are not supported against OAuth-gated remote ba
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/95a839ac19bbecc9.
Report an issue: GitHub.