stablyai/orca · error · BrowserError

browser_debugger_detached

browser_debugger_detached

Error message

The active browser tab was closed. Run 'orca tab list' to find remaining tabs.

What it means

After auto-selecting the single registered tab, getActiveGuest() calls webContents.fromId() and checks isDestroyed(). If the tab was destroyed between the registry iteration and the fromId call, this is a teardown race — the tab existed in the registry but its renderer was torn down in the gap. activeWebContentsId is reset to null so the next command can attempt re-selection. The message directs the user to tab list to find remaining tabs.

Source

Thrown at src/main/browser/cdp-bridge.ts:1052

    if (tabs.length === 0) {
      throw new BrowserError(
        'browser_no_tab',
        'No browser tab is open. Use the Orca UI to open a browser tab first.'
      )
    }
    if (tabs.length === 1) {
      this.activeWebContentsId = tabs[0][1]
    } else {
      throw new BrowserError(
        'browser_no_tab',
        "Multiple browser tabs are open. Run 'orca tab list' and 'orca tab switch --index <n>' to select one."
      )
    }

    const guest = webContents.fromId(this.activeWebContentsId!)
    if (!guest || guest.isDestroyed()) {
      this.activeWebContentsId = null
      throw new BrowserError(
        'browser_debugger_detached',
        "The active browser tab was closed. Run 'orca tab list' to find remaining tabs."
      )
    }
    return guest
  }

  private getRegisteredTabs(): Map<string, number> {
    // Why: reach into BrowserManager's private tab map since it exposes no public listTabs().
    return (this.browserManager as unknown as { webContentsIdByTabId: Map<string, number> })
      .webContentsIdByTabId
  }

  private resolveTabId(webContentsId: number): string {
    for (const [tabId, wcId] of this.getRegisteredTabs()) {
      if (wcId === webContentsId) {
        return tabId
      }

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Run 'orca tab list' to see if any tabs remain.
  2. Open a new browser tab if none remain, then retry.
  3. Retry the command — the stale activeWebContentsId has been cleared, allowing re-selection.
Defensive patterns

Strategy: retry

Validate before calling

const { tabs } = bridge.tabList()
if (tabs.length === 0) {
  throw new Error('No live tab available — open one before retrying')
}
await bridge.click(ref)

Try / catch

try {
  await bridge.click(ref)
} catch (e) {
  if (e instanceof BrowserError && e.code === 'browser_debugger_detached' && /closed/.test(e.message)) {
    // activeWebContentsId was cleared; re-select from remaining tabs and retry
    const { tabs } = bridge.tabList()
    if (tabs.length > 0) {
      await bridge.tabSwitch(tabs.length - 1)
      await bridge.click(ref)
    }
  }
  throw e
}

Prevention

When it happens

Trigger: The single open tab is closed or its renderer crashes in the window between getRegisteredTabs() and webContents.fromId(); a process swap invalidates the id mid-call.

Common situations: Timing races during tab teardown; renderer crash on a heavy page; user closes the tab at the exact moment a command is issued.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/ffd5fec15bdee8cc. Report an issue: GitHub.