CherryHQ/cherry-studio · error · Error

Tab 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.

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.

Appendix: 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)