CherryHQ/cherry-studio · error · Error

Tab ${tabId} not found

Error message

Tab ${tabId} not found

What it means

After resolving a window for the mode, switchTab looks up windowInfo.tabs.get(tabId); if absent it throws 'Tab ${tabId} not found'. The tab may have been closed (webContents 'destroyed' deletes the entry) or the tabId was never created in that window.

Source

Thrown at src/main/ai/mcp/servers/browser/controller.ts:931

   * @param privateMode - If true, closes tab from private window (default: false)
   * @param tabId - Tab identifier to close
   */
  public async closeTab(privateMode: boolean, tabId: string) {
    await this.reset(privateMode, tabId)
  }

  /**
   * Switches the active tab
   * @param privateMode - If true, switches tab in private window (default: false)
   * @param tabId - Tab identifier to switch to
   */
  public async switchTab(privateMode: boolean, tabId: string) {
    const windowKey = this.getWindowKey(privateMode)
    const windowInfo = this.windows.get(windowKey)
    if (!windowInfo) throw new Error(`Window not found for ${privateMode ? 'private' : 'normal'} mode`)

    const tab = windowInfo.tabs.get(tabId)
    if (!tab) throw new Error(`Tab ${tabId} not found`)

    // Remove previous active tab view (but NOT the tabBarView)
    if (windowInfo.activeTabId && windowInfo.activeTabId !== tabId) {
      const prevTab = windowInfo.tabs.get(windowInfo.activeTabId)
      if (prevTab && !windowInfo.window.isDestroyed()) {
        windowInfo.window.removeBrowserView(prevTab.view)
      }
    }

    windowInfo.activeTabId = tabId

    // Add the new active tab view
    if (!windowInfo.window.isDestroyed()) {
      windowInfo.window.addBrowserView(tab.view)
      this.updateViewBounds(windowInfo)
    }

    this.touchTab(windowKey, tabId)

View on GitHub (pinned to 726446b54c)

Solutions

  1. Validate the tabId against the window's current tabs before calling switchTab; re-enumerate if stale.
  2. Ensure the privateMode flag matches the mode the tabId was created under.
  3. Handle 'tab not found' by falling back to the active tab or creating a fresh one.
  4. Avoid caching tabIds long-term; re-query the controller's list_tabs.

Example fix

// before
const tab = windowInfo.tabs.get(tabId)
if (!tab) throw new Error(`Tab ${tabId} not found`)

// after — fall back to active tab or create one
let tab = windowInfo.tabs.get(tabId)
if (!tab) {
  if (windowInfo.activeTabId) {
    tab = windowInfo.tabs.get(windowInfo.activeTabId)
  }
  if (!tab) {
    const created = await this.createTab(privateMode, false)
    tab = windowInfo.tabs.get(created.tabId)
  }
  if (!tab) throw new Error(`Tab ${tabId} not found and no fallback available`)
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify the tabId is live in the target window before switching
function tabExists(controller, privateMode, tabId) {
  const windowKey = controller.getWindowKey(privateMode)
  const windowInfo = controller.windows.get(windowKey)
  return !!windowInfo && windowInfo.tabs.has(tabId) && !windowInfo.tabs.get(tabId).view.webContents.isDestroyed()
}

Type guard

function isLiveTab(windowInfo, tabId): boolean {
  const t = windowInfo?.tabs?.get(tabId)
  return !!t && !t.view.webContents.isDestroyed()
}

Try / catch

if (!tabExists(controller, privateMode, tabId)) {
  return { content: [{ type: 'text', text: `Tab ${tabId} no longer exists` }], isError: true }
}

Prevention

When it happens

Trigger: Agent passes a tabId from a different window/mode; the tab was already closed via closeTab/closeTabInternal; the webContents 'destroyed' event removed it; or a stale tabId cached by the agent.

Common situations: Agent holds an old tabId across a reset/close; mode mismatch (normal-mode tabId used in private mode); tab crashed and auto-removed; concurrent closeTab raced switchTab.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/51811b4536c7c576. Report an issue: GitHub.