microsoft/playwright · error · Error
This tab is already connected to another client
Error message
This tab is already connected to another client
What it means
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().
Source
Thrown at packages/extension/src/background.ts:107
});
return false;
case 'disconnect':
this._connections.get(message.connectionId)?.close('User disconnected');
sendResponse({ success: true });
return false;
case 'keepalive':
// Connect page pings us every ~20s so receiving this message resets
// the MV3 service worker idle timer and keeps the relay WebSocket alive.
return false;
}
}
private async _connectTab(selectorTabId: number, tab: chrome.tabs.Tab & { id: number }, clientName: string | undefined): Promise<void> {
try {
await this._cleanupPromise;
this._releaseTab(selectorTabId);
if (tab.id !== selectorTabId && this._connectedTabIds().has(tab.id))
throw new Error('This tab is already connected to another client');
const connection = await this._pendingConnections.take(selectorTabId);
if (!connection)
throw new Error('Pending client connection closed');
const id = ++this._lastConnectionId;
const taken = [...this._connections.values()].map(group => group.groupStyle);
const group = new ConnectedTabGroup(connection, tab, clientName, uniqueGroupStyle(clientName, taken), tabId => this._pendingConnections.has(tabId));
group.onclose = () => this._connections.delete(id);
this._connections.set(id, group);
await Promise.all([
chrome.tabs.update(tab.id, { active: true }),
chrome.windows.update(tab.windowId, { focused: true }),
]).catch(() => {});
if (tab.id !== selectorTabId)
await chrome.tabs.remove(selectorTabId).catch(() => {});View on GitHub (pinned to 9642f57665)
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.
Example fix
// before: second client selects a tab already owned by client A
await chrome.runtime.sendMessage({ type: 'connectToTab', tab: alreadyConnectedTab, clientName: 'client B' });
// -> Error: This tab is already connected to another client
// after: check status and disconnect the previous owner first
const { connections } = await chrome.runtime.sendMessage({ type: 'getConnectionStatus' });
const owner = connections.find((c: any) => c.connectedTabIds.includes(alreadyConnectedTab.id!));
if (owner)
await chrome.runtime.sendMessage({ type: 'disconnect', connectionId: owner.id });
await chrome.runtime.sendMessage({ type: 'connectToTab', tab: alreadyConnectedTab, clientName: 'client B' }); Defensive patterns
Strategy: validation
Validate before calling
// Before sending 'connectToTab', ask the extension which tabs are taken
const { connections } = await chrome.runtime.sendMessage({ type: 'getConnectionStatus' });
const takenTabIds = new Set<number>(
connections.flatMap((c: any) => c.connectedTabIds as number[]),
);
if (takenTabIds.has(targetTab.id!)) {
const owner = connections.find((c: any) => c.connectedTabIds.includes(targetTab.id!));
// either disconnect the owner or pick another tab
await chrome.runtime.sendMessage({ type: 'disconnect', connectionId: owner.id });
}
await chrome.runtime.sendMessage({ type: 'connectToTab', tab: targetTab, clientName }); Try / catch
// The error arrives in the sendResponse payload, not as a thrown exception
const res = await chrome.runtime.sendMessage({ type: 'connectToTab', tab: targetTab, clientName });
if (!res.success && /already connected to another client/.test(res.error)) {
// surface a 'disconnect previous client or choose another tab' action instead of retrying blindly
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Playwright Extension not found in "${userDataDir}". Install
- No attached tab to forward browser-level command: ${method}
- Device emulation is not supported with cdpEndpoint.
- No open pages available.
- Tab ${index} not found
AI-assisted analysis of microsoft/playwright@9642f57665 (2026-08-21).
Data as JSON: /api/errors/473f18e39edd9638.
Report an issue: GitHub.