{"record":{"id":"473f18e39edd9638","repo":"microsoft/playwright","slug":"this-tab-is-already-connected-to-another-client","errorCode":null,"errorMessage":"This tab is already connected to another client","messagePattern":"This tab is already connected to another client","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/extension/src/background.ts","lineNumber":107,"sourceCode":"        });\n        return false;\n      case 'disconnect':\n        this._connections.get(message.connectionId)?.close('User disconnected');\n        sendResponse({ success: true });\n        return false;\n      case 'keepalive':\n        // Connect page pings us every ~20s so receiving this message resets\n        // the MV3 service worker idle timer and keeps the relay WebSocket alive.\n        return false;\n    }\n  }\n\n  private async _connectTab(selectorTabId: number, tab: chrome.tabs.Tab & { id: number }, clientName: string | undefined): Promise<void> {\n    try {\n      await this._cleanupPromise;\n      this._releaseTab(selectorTabId);\n      if (tab.id !== selectorTabId && this._connectedTabIds().has(tab.id))\n        throw new Error('This tab is already connected to another client');\n\n      const connection = await this._pendingConnections.take(selectorTabId);\n      if (!connection)\n        throw new Error('Pending client connection closed');\n\n      const id = ++this._lastConnectionId;\n      const taken = [...this._connections.values()].map(group => group.groupStyle);\n      const group = new ConnectedTabGroup(connection, tab, clientName, uniqueGroupStyle(clientName, taken), tabId => this._pendingConnections.has(tabId));\n      group.onclose = () => this._connections.delete(id);\n      this._connections.set(id, group);\n\n      await Promise.all([\n        chrome.tabs.update(tab.id, { active: true }),\n        chrome.windows.update(tab.windowId, { focused: true }),\n      ]).catch(() => {});\n\n      if (tab.id !== selectorTabId)\n        await chrome.tabs.remove(selectorTabId).catch(() => {});","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/microsoft/playwright/blob/9642f57665db582b12dcfa5d8022808f2402fa2a/packages/extension/src/background.ts#L89-L125","documentation":"Thrown by the Playwright Chrome extension's MV3 background service worker when a connect request targets a browser tab that already belongs to another client's ConnectedTabGroup. The extension relays a tab's CDP traffic to one client at a time, so a second client (another MCP session, inspector, or connect page) cannot claim the same tab. The check at background.ts:106 fires when the chosen tab is not the selector page itself and its id already appears in _connectedTabIds().","triggerScenarios":"A connect page (selector tab) sends the 'connectToTab' runtime message (background.ts:71-77) with a tab whose id is already inside another group returned by _connectedTabIds(). Concretely: two `playwright` MCP/CDP clients connect through the extension relay and both pick the same target tab; or a previous session's group is still open (not disconnected via 'disconnect', background.ts:91-93) while a new client selects that tab.","commonSituations":"Running two editors/agents (e.g. two MCP servers) that both use the extension against the same page; reconnecting after a client crash without disconnecting the old group; a stale connect page left open from an earlier run; selecting a tab that a still-active earlier client already claimed. Note the guard `await this._cleanupPromise` only reconciles groups orphaned by service-worker restarts, not live ones.","solutions":["Disconnect the existing client first: call the extension's 'disconnect' message with the connectionId from 'getConnectionStatus' (background.ts:82-93), or close the old connect page/client, then retry connecting.","Connect to a different tab that is not yet in any group's connectedTabIds.","If the owning client is gone but the group lingers, reload the extension (or wait for the MV3 service worker restart plus stale-group cleanup via cleanupStalePlaywrightGroups) so the old group is released."],"exampleFix":"// before: second client selects a tab already owned by client A\nawait chrome.runtime.sendMessage({ type: 'connectToTab', tab: alreadyConnectedTab, clientName: 'client B' });\n// -> Error: This tab is already connected to another client\n\n// after: check status and disconnect the previous owner first\nconst { connections } = await chrome.runtime.sendMessage({ type: 'getConnectionStatus' });\nconst owner = connections.find((c: any) => c.connectedTabIds.includes(alreadyConnectedTab.id!));\nif (owner)\n  await chrome.runtime.sendMessage({ type: 'disconnect', connectionId: owner.id });\nawait chrome.runtime.sendMessage({ type: 'connectToTab', tab: alreadyConnectedTab, clientName: 'client B' });","handlingStrategy":"validation","validationCode":"// Before sending 'connectToTab', ask the extension which tabs are taken\nconst { connections } = await chrome.runtime.sendMessage({ type: 'getConnectionStatus' });\nconst takenTabIds = new Set<number>(\n  connections.flatMap((c: any) => c.connectedTabIds as number[]),\n);\nif (takenTabIds.has(targetTab.id!)) {\n  const owner = connections.find((c: any) => c.connectedTabIds.includes(targetTab.id!));\n  // either disconnect the owner or pick another tab\n  await chrome.runtime.sendMessage({ type: 'disconnect', connectionId: owner.id });\n}\nawait chrome.runtime.sendMessage({ type: 'connectToTab', tab: targetTab, clientName });","typeGuard":null,"tryCatchPattern":"// The error arrives in the sendResponse payload, not as a thrown exception\nconst res = await chrome.runtime.sendMessage({ type: 'connectToTab', tab: targetTab, clientName });\nif (!res.success && /already connected to another client/.test(res.error)) {\n  // surface a 'disconnect previous client or choose another tab' action instead of retrying blindly\n}","preventionTips":["Keep one extension-relay client per tab; disconnect ('disconnect' message or close the connect page) before connecting a new client.","Check 'getConnectionStatus' before rendering tab choices so already-connected tabs are visibly excluded.","Close stale connect pages from crashed sessions before starting new ones."],"tags":["browser-extension","chrome","tabs","cdp","mcp","connection-management"],"backgroundTag":"resource-already-in-use","analyzedSha":"9642f57665db582b12dcfa5d8022808f2402fa2a","analyzedAt":"2026-08-21T16:56:32.694Z","contentChangedAt":"2026-08-21T16:56:32.694Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}