musistudio/claude-code-router · error · Error

Failed to handle browser dialog: ${formatError(error)}

Error message

Failed to handle browser dialog: ${formatError(error)}

What it means

A wrapper error thrown when handling a browser dialog (alert/confirm/prompt) via the Electron debugger protocol fails for any underlying reason — the inner error is captured and re-formatted into the message. Root causes include the debugger failing to attach, the dialog already being dismissed, or an unexpected CDP response.

Source

Thrown at packages/electron/src/main/browser-automation-mcp.ts:2310

    await withTimeout(webContents.debugger.sendCommand("Page.enable"), defaultJavascriptTimeoutMs, "Timed out enabling browser dialog handling.");
    await withTimeout(
      webContents.debugger.sendCommand("Page.handleJavaScriptDialog", {
        accept,
        ...(promptText !== undefined ? { promptText } : {})
      }),
      defaultJavascriptTimeoutMs,
      "Timed out handling browser dialog."
    );
    return {
      accepted: accept,
      ok: true,
      promptText,
      session,
      title: await webContents.getTitle(),
      url: webContents.getURL()
    };
  } catch (error) {
    throw new Error(`Failed to handle browser dialog: ${formatError(error)}`);
  } finally {
    if (attachedHere && webContents.debugger.isAttached()) {
      try {
        webContents.debugger.detach();
      } catch {
        // Ignore detach errors after the dialog is resolved.
      }
    }
  }
}

async function executeJavaScriptWithTimeout<T = unknown>(
  webContents: WebContents,
  script: string,
  timeoutMs: number,
  label: string
): Promise<T> {
  if (webContents.isDestroyed()) {

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Read the embedded inner error text — it identifies the actual CDP/debugger failure to fix.
  2. Retry once: dialogs are inherently racy and a retry after re-detecting the dialog often succeeds.
  3. Ensure no other debugger session (open DevTools, another tool call) holds the webContents.
  4. Avoid firing multiple dialog-handling calls concurrently on the same tab.

Example fix

// before
await call("browser_handle_dialog", { accept: true, tabId }); // throws if dialog already gone

// after
try {
  await call("browser_handle_dialog", { accept: true, tabId });
} catch (e) {
  if (String(e.message).includes("Failed to handle browser dialog")) {
    const state = await call("browser_snapshot", { tabId }); // dialog already resolved; continue
  } else throw e;
}
Defensive patterns

Strategy: retry

Try / catch

try { await call("browser_handle_dialog", { accept: true, tabId }); } catch (e) { if (e instanceof Error && e.message.startsWith("Failed to handle browser dialog")) { const snapshot = await call("browser_snapshot", { tabId }); /* dialog likely already resolved; inspect and continue */ } else throw e; }

Prevention

When it happens

Trigger: Calling a browser dialog tool while the debugger cannot attach to the webContents (already attached elsewhere), when the dialog no longer exists by the time the handler acts, or when the CDP command errors/times out.

Common situations: Race between dialog appearance and the handle call; another debugger client (DevTools) is attached; page dismissed the dialog programmatically; concurrent dialog-handling calls on the same tab.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/2042837083225912. Report an issue: GitHub.