{"record":{"id":"6e3d9456e8f3ef67","repo":"CherryHQ/cherry-studio","slug":"mcp-browser-window-not-found-after-open","errorCode":null,"errorMessage":"MCP browser window not found after open","messagePattern":"MCP browser window not found after open","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/browser/controller.ts","lineNumber":368,"sourceCode":"  }\n\n  private async createBrowserWindow(\n    windowKey: string,\n    privateMode: boolean,\n    showWindow = false\n  ): Promise<{ window: BrowserWindow; windowId: string }> {\n    await this.ensureAppReady()\n\n    const windowManager = application.get('WindowManager')\n    // The per-mode session partition is the only dynamic option; everything else\n    // lives in the WindowType.McpBrowser registry entry.\n    const windowId = windowManager.open(WindowType.McpBrowser, {\n      options: { webPreferences: { partition: this.getPartition(privateMode) } }\n    })\n    const win = windowManager.getWindow(windowId)\n    if (!win) {\n      windowManager.close(windowId)\n      throw new Error('MCP browser window not found after open')\n    }\n    if (showWindow) win.show()\n\n    win.on('closed', () => {\n      const windowInfo = this.windows.get(windowKey)\n      if (windowInfo) {\n        const tabIds = Array.from(windowInfo.tabs.keys())\n        for (const tabId of tabIds) {\n          this.closeTabInternal(windowInfo, tabId)\n        }\n        this.windows.delete(windowKey)\n      }\n    })\n\n    return { window: win, windowId }\n  }\n\n  private async getOrCreateWindow(privateMode: boolean, showWindow = false): Promise<WindowInfo> {","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/browser/controller.ts#L350-L386","documentation":"In createBrowserWindow, windowManager.open() returns a windowId but the immediately following windowManager.getWindow(windowId) returns null/undefined. This means the McpBrowser window was destroyed or removed from the registry between open and lookup — an invariant violation of the WindowManager contract.","triggerScenarios":"WindowType.McpBrowser registry entry is misconfigured; the open() triggered an immediate close (e.g. a 'closed' event fired synchronously during open); WindowManager pool exhausted and the window was reclaimed instantly; or a concurrent dispose() raced the creation.","commonSituations":"App shutting down while a browser tool call arrives; multiple concurrent browser-tool invocations stressing the window pool; a malformed McpBrowser registry entry; Electron failed to construct the BrowserWindow (GPU crash) and it was destroyed on creation.","solutions":["Check the WindowType.McpBrowser entry in windowRegistry.ts for correct mode/lifecycle settings.","Ensure no concurrent dispose()/shutdown is racing the call — gate browser-tool calls on app readiness (ensureAppReady is already called).","Inspect logs for a synchronously-fired 'closed' event or GPU/process crash on the McpBrowser window.","Retry once after a short delay; if it persists, report a WindowManager state corruption.","Limit concurrent createBrowserWindow calls to avoid pool exhaustion."],"exampleFix":"// before\nconst windowId = windowManager.open(WindowType.McpBrowser, { options: { webPreferences: { partition } } })\nconst win = windowManager.getWindow(windowId)\nif (!win) {\n  windowManager.close(windowId)\n  throw new Error('MCP browser window not found after open')\n}\n\n// after — retry once and surface the underlying registry/WM failure\nlet win = windowManager.getWindow(windowId)\nif (!win) {\n  await new Promise((r) => setTimeout(r, 50))\n  win = windowManager.getWindow(windowId)\n}\nif (!win) {\n  throw new Error(`MCP browser window ${windowId} not found after open; McpBrowser registry or pool may be misconfigured`)\n}","handlingStrategy":"try-catch","validationCode":"// Guard browser-window creation against an immediately-vanishing window\nasync function openBrowserWindow(windowManager, privateMode) {\n  const windowId = windowManager.open(WindowType.McpBrowser, { options: { webPreferences: { partition: getPartition(privateMode) } } })\n  let win = windowManager.getWindow(windowId)\n  if (!win) {\n    await new Promise((r) => setTimeout(r, 50))\n    win = windowManager.getWindow(windowId)\n  }\n  if (!win) throw new Error(`MCP browser window ${windowId} not available; McpBrowser registry or pool issue`)\n  return { win, windowId }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await openBrowserWindow(windowManager, privateMode)\n} catch (e) {\n  logger.error('McpBrowser window creation failed', { error: e.message })\n  throw e\n}","preventionTips":["Gate browser-tool calls on app readiness and not-disposing.","Limit concurrent createBrowserWindow calls to avoid pool exhaustion.","Verify the WindowType.McpBrowser registry entry is correct."],"tags":["electron","window-manager","browser-tool","race"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}