fix(extension): reconcile-based tab group management#40324
Merged
Conversation
When the MCP client disconnects, Chrome eventually dissolves the Playwright group because all tabs are removed from it. The extension was not clearing _groupId on close, so on reconnect it tried to add the newly-attached tab to the stale (now dissolved) group. The fallback creates a new group, but during the async window the _onTabUpdated handler observes the new groupId != the stale _groupId and detaches the tab. Reset _groupId in the onclose callback so the next connection starts from a clean slate.
When a user drags a tab with a chrome://, edge:// or devtools:// URL into the Playwright group, chrome.debugger.attach cannot be attached, so the tab would remain in the group without being controlled. Detect these schemes in _onTabUpdated and ungroup the tab immediately. chrome.tabs.ungroup also rejects with "user may be dragging a tab" during an active drag, so _ungroupTab retries with a short delay, mirroring _addTabToGroup.
Service worker restarts lose all connection state, so any Playwright tab groups from a previous session are stale. Query for groups with that title on startup and ungroup their tabs so the user does not end up with orphan groups after a reload.
Run _cleanupStaleGroups through _groupQueue so a new connection's _addTabToGroup call waits for cleanup to finish, preventing cleanup from ungrouping freshly-added tabs.
…of truth Replace the scattered _addTabToGroup/_ungroupTab calls with a single _reconcile() method that brings Chrome's Playwright group in line with the desired members (_connectedTabIds + _selectorTabId). - Serialized via _reconcileQueue so concurrent calls can't race. - Retries on drag errors with 0/100/200ms ramp, then 400ms steady. - Suppresses _onTabUpdated handling while reconcile mutates the group, so Chrome's synthetic onUpdated events don't fight the reconciler. - Detects dissolved stale groups via chrome.tabGroups.get and resets _groupId so the next reconcile creates a fresh group.
…roup The selector tab was previously tracked separately and added to the Playwright group via _selectorTabId. Remove that field — the connect page is no longer part of the group. Update the corresponding test to assert the connected tab is grouped and the connect page is not. Also make the reconnect-group test's evaluate resilient to a momentarily-unavailable chrome.tabs binding during page transitions.
…rhead - Reuse _disconnect in _connectTab instead of duplicating close/cleanup. The onclose callback handles state clearing, so _disconnect is now a thin wrapper taking an optional reason. - Extract PLAYWRIGHT_GROUP_TITLE/_COLOR constants and an isNonDebuggableUrl helper; de-duplicate scheme list and title string. - Skip _reconcile in _onTabUpdated when Chrome's view already matches our desired state (inOurGroup === connected). - Parallelize _updateBadge's three chrome.action calls. - Parallelize per-group tab queries in _cleanupStaleGroups and the group existence + membership checks in _reconcileOnce.
pavelfeldman
approved these changes
Apr 20, 2026
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Chrome resets per-tab action state on navigation, so connected tabs lose the ✓ badge when they navigate (e.g., after browser_tabs new). Re-apply the badge in _onTabUpdated for tabs that are in _connectedTabIds. Extracted the badge payload to a CONNECTED_BADGE constant to keep the badge in sync between ontabattached and _onTabUpdated.
Contributor
Test results for "MCP"6434 passed, 976 skipped Merge workflow run. |
Contributor
Test results for "tests 1"3 flaky39248 passed, 847 skipped Merge workflow run. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_addTabToGroup/_ungroupTab/_selectorTabIdtracking with a single_reconcile()that drives Chrome's Playwright group from_connectedTabIds. Serialized via a promise queue, retries with 0/100/200ms ramp then 400ms steady on drag errors, and detects dissolved groups viachrome.tabGroups.get._groupIdon connection close so reconnecting starts fresh instead of targeting a dissolved group.chrome:,edge:,devtools:) dragged into the group without attempting a round-trip attach that would fail.Tests: new
tab is re-added to Playwright group after reconnectingandchrome:// tab dragged into group is automatically ungrouped; updatedconnected tab is in green Playwright group, connect page is not.