Splits out the confirmed, reproducible part of #41843 into a focused issue, as requested there. Two distinct, independently reproducible causes of the same symptom: after a reconnect through the extension (--extension), the freshly spawned connect.html seed tab is left behind — orphaned — while a separate new tab opens to serve the actual request.
Cause 1 — first tool call after reconnect is browser_tabs new
Repro (5/5):
- Connect via the extension, token-bypass flow.
- Force a reconnect (e.g. close the group's last tab, which tears the connection down per
_checkLastTabDetached).
- Make the first tool call of the new connection
browser_tabs {action: "new", url}.
Expected: the seed connect.html tab is reused or closed; only the requested tab remains.
Actual: Context.newTab() creates a second tab by design; the seed connect.html tab is left behind, alive and grouped.
09:37:03.784 pw:mcp:relay Extension connection established
09:37:03.837 pw:mcp:relay ← Playwright: Target.createTarget (id=19)
Root cause: Context.newTab() (packages/playwright-core/src/tools/backend/context.ts) has no awareness of the seed tab spawned by the token-bypass reconnect path, so it always creates a new tab rather than checking whether the current tab is just connection infrastructure.
Suggested fix: when Context.newTab() creates a tab, close any remaining page whose URL matches the extension's own connect.html?mcpRelayUrl= seed signature — that page is connection infrastructure, never a user target.
Cause 2 — the spawned connect page doesn't stay isolated from other tab groups
Repro (2/2, requires a second active Playwright connection/group already open):
- Have an existing Playwright extension connection/tab group active.
- Start a second, separate extension connection (new client) while the first is active.
Expected: the new connection's own connect.html seed tab attaches cleanly to its own connection, without interacting with any other connection's tab group.
Actual: the newly spawned tab doesn't stay out of tab-group territory it has no business being in. The exact mechanism isn't fully pinned down yet (likely Chrome inserting the new tab next to the active one and carrying over its group, but that's an inference, not confirmed) — what is confirmed, 2/2, is that when another Playwright connection's group happens to be sitting adjacent, the new tab ends up inside that neighboring group instead of staying ungrouped. That group's _onTabGroupChanged then attaches the new page, and the rightful connection's own attach fails — silently:
09:44:38.428 pw:mcp:relay Extension connection established
09:44:38.437 pw:mcp:error attach failed for tab 564270754
(chrome-extension://.../connect.html?mcpRelayUrl=ws%3A%2F%2F...):
Error: Another debugger is already attached to the tab with id: 564270754.
09:44:38.437 pw:mcp:error ANOMALY: setAutoAttach answered with 0 attached of 1 known tabs
09:44:38.439 pw:mcp:relay ← Playwright: Target.createTarget (id=5)
BrowserModel.enableAutoAttach() (packages/playwright-core/src/tools/mcp/browserModel.ts) swallows per-tab attach failures (.catch(logUnhandledError)) and reports Target.setAutoAttach successfully with an empty model, so Playwright sees zero pages and creates a fresh tab — ghosting the seed again.
A related swallowed-failure case, same shape, no second group needed — a load race where the seed tab is attached while still on its pre-navigation URL:
09:57:45.410 pw:mcp:error attach failed for tab 564270775 ():
Error: Cannot access a chrome:// URL
(url was still empty/pending — chrome://new-tab-page under the hood — when chrome.debugger.attach arrived; isNonDebuggableUrl(undefined) is false, so nothing filtered it.)
Suggested fixes:
- The extension should never auto-attach its own connect page when it enters a group (
url.startsWith(chrome.runtime.getURL('connect.html'))).
enableAutoAttach() should not report silent success when 0 of N known tabs attached — retry once (covers the load race) and/or surface the failure.
Environment
- Windows 11, branded Chrome (extension mode via
--extension)
- Both reproduced with
DEBUG=pw:mcp:* logging on a local build whose relevant code paths are unmodified from upstream.
Happy to send PRs for either fix once triaged.
Splits out the confirmed, reproducible part of #41843 into a focused issue, as requested there. Two distinct, independently reproducible causes of the same symptom: after a reconnect through the extension (
--extension), the freshly spawnedconnect.htmlseed tab is left behind — orphaned — while a separate new tab opens to serve the actual request.Cause 1 — first tool call after reconnect is
browser_tabs newRepro (5/5):
_checkLastTabDetached).browser_tabs {action: "new", url}.Expected: the seed
connect.htmltab is reused or closed; only the requested tab remains.Actual:
Context.newTab()creates a second tab by design; the seedconnect.htmltab is left behind, alive and grouped.Root cause:
Context.newTab()(packages/playwright-core/src/tools/backend/context.ts) has no awareness of the seed tab spawned by the token-bypass reconnect path, so it always creates a new tab rather than checking whether the current tab is just connection infrastructure.Suggested fix: when
Context.newTab()creates a tab, close any remaining page whose URL matches the extension's ownconnect.html?mcpRelayUrl=seed signature — that page is connection infrastructure, never a user target.Cause 2 — the spawned connect page doesn't stay isolated from other tab groups
Repro (2/2, requires a second active Playwright connection/group already open):
Expected: the new connection's own
connect.htmlseed tab attaches cleanly to its own connection, without interacting with any other connection's tab group.Actual: the newly spawned tab doesn't stay out of tab-group territory it has no business being in. The exact mechanism isn't fully pinned down yet (likely Chrome inserting the new tab next to the active one and carrying over its group, but that's an inference, not confirmed) — what is confirmed, 2/2, is that when another Playwright connection's group happens to be sitting adjacent, the new tab ends up inside that neighboring group instead of staying ungrouped. That group's
_onTabGroupChangedthen attaches the new page, and the rightful connection's own attach fails — silently:BrowserModel.enableAutoAttach()(packages/playwright-core/src/tools/mcp/browserModel.ts) swallows per-tab attach failures (.catch(logUnhandledError)) and reportsTarget.setAutoAttachsuccessfully with an empty model, so Playwright sees zero pages and creates a fresh tab — ghosting the seed again.A related swallowed-failure case, same shape, no second group needed — a load race where the seed tab is attached while still on its pre-navigation URL:
(
urlwas still empty/pending —chrome://new-tab-pageunder the hood — whenchrome.debugger.attacharrived;isNonDebuggableUrl(undefined)isfalse, so nothing filtered it.)Suggested fixes:
url.startsWith(chrome.runtime.getURL('connect.html'))).enableAutoAttach()should not report silent success when 0 of N known tabs attached — retry once (covers the load race) and/or surface the failure.Environment
--extension)DEBUG=pw:mcp:*logging on a local build whose relevant code paths are unmodified from upstream.Happy to send PRs for either fix once triaged.