fix: keep a still-open selected page instead of falling back to the first page#2328
Merged
OrKoN merged 1 commit intoJul 9, 2026
Conversation
1 task
OrKoN
enabled auto-merge
July 9, 2026 06:14
Merged
via the queue into
ChromeDevTools:main
with commit Jul 9, 2026
c645eee
28 of 30 checks passed
pull Bot
pushed a commit
to edisplay/chrome-devtools-mcp
that referenced
this pull request
Jul 10, 2026
Follow-up to ChromeDevTools#2304, and to ChromeDevTools#2328 which fixed the symptom. After ChromeDevTools#2333, `getPages()` – what `list_pages` presents – is a filtered view over `#mcpPages` that excludes `devtools://` frontends (unless `experimentalDevToolsDebugging` is set; they enter `#mcpPages` via `handleDevToolsAsPage`). `getPageById()` still searches all of `#mcpPages`, though – so `select_page`, and every other tool that takes a `pageId`, can target a page `list_pages` never showed. ChromeDevTools#2328 stopped that from silently stealing a still-open selection. This goes to the root: `getPageById()` now resolves through `getPages()`, so an unlisted page isn't targetable in the first place. Page ids only ever reach the client through the listing, so an unlisted id has no legitimate source. `experimentalDevToolsDebugging` is unaffected – the listing already includes devtools frontends there. All five callers (`select_page`, `close_page`, `evaluate_script`, `get_tab_id`, and the generic `pageId` handler) benefit uniformly. Also trims the now-impossible example from the fallback comment – an unlisted `devtools://` page can no longer be selected. Test: with DevTools open, the frontend page is tracked but unlisted; the new test asserts no id outside `getPages()` resolves through `getPageById()`. Refs: ChromeDevTools#2304
kku1993
pushed a commit
to kku1993/chrome-devtools-mcp
that referenced
this pull request
Jul 14, 2026
🤖 I have created a release *beep* *boop* --- ## [1.6.0](ChromeDevTools/chrome-devtools-mcp@chrome-devtools-mcp-v1.5.0...chrome-devtools-mcp-v1.6.0) (2026-07-14) ### 🎉 Features * add experimentalGcfFormat flag for GCF-encoded tool responses ([ChromeDevTools#2235](ChromeDevTools#2235)) ([3d21389](ChromeDevTools@3d21389)) * Print object count and total sizes in get_heapsnapshot_details ([ChromeDevTools#2325](ChromeDevTools#2325)) ([15a6b78](ChromeDevTools@15a6b78)) * support --allow-unrestricted-paths configuration ([ChromeDevTools#2296](ChromeDevTools#2296)) ([6e56c02](ChromeDevTools@6e56c02)) * Support filter with heap snapshots aggregates ([ChromeDevTools#2323](ChromeDevTools#2323)) ([2812902](ChromeDevTools@2812902)) * update Lighthouse to 13.4.0 ([ChromeDevTools#2317](ChromeDevTools#2317)) ([ffc6060](ChromeDevTools@ffc6060)) ### 🛠️ Fixes * enforce .gz instead of json.gz in performance tools ([ChromeDevTools#2305](ChromeDevTools#2305)) ([b06e39b](ChromeDevTools@b06e39b)) * keep a still-open selected page instead of falling back to the first page ([ChromeDevTools#2328](ChromeDevTools#2328)) ([c645eee](ChromeDevTools@c645eee)), closes [ChromeDevTools#2304](ChromeDevTools#2304) * keep page ids unique across browser reconnects ([ChromeDevTools#2345](ChromeDevTools#2345)) ([3e8d922](ChromeDevTools@3e8d922)) * paginate page 0 in list_network_requests and list_console_messages ([ChromeDevTools#2359](ChromeDevTools#2359)) ([d0025b3](ChromeDevTools@d0025b3)) * release held modifiers when press_key key event fails ([ChromeDevTools#2347](ChromeDevTools#2347)) ([78ccb19](ChromeDevTools@78ccb19)) * report when the selected page was auto-replaced by the fallback ([ChromeDevTools#2308](ChromeDevTools#2308)) ([2c16ac3](ChromeDevTools@2c16ac3)), closes [ChromeDevTools#2304](ChromeDevTools#2304) * resolve page ids only among listed pages ([ChromeDevTools#2332](ChromeDevTools#2332)) ([eb04951](ChromeDevTools@eb04951)), closes [ChromeDevTools#2304](ChromeDevTools#2304) * **snapshot:** resolve element ids on the correct snapshot ([ChromeDevTools#2295](ChromeDevTools#2295)) ([b703f2c](ChromeDevTools@b703f2c)) * **telemetry:** resolve enum values through nested schema wrappers ([ChromeDevTools#2315](ChromeDevTools#2315)) ([c065fd9](ChromeDevTools@c065fd9)) * Wait until daemon is started ([ChromeDevTools#2327](ChromeDevTools#2327)) ([ed7e95d](ChromeDevTools@ed7e95d)) ### 📄 Documentation * add Grok Build CLI configuration section ([ChromeDevTools#2294](ChromeDevTools#2294)) ([aa4be07](ChromeDevTools@aa4be07)) * update memory leak debugging skill ([ChromeDevTools#2330](ChromeDevTools#2330)) ([c1736a0](ChromeDevTools@c1736a0)) ### ⚡ Performance * concurrent I/O in Root Path Resolution ([ChromeDevTools#2279](ChromeDevTools#2279)) ([b2c63e6](ChromeDevTools@b2c63e6)) ### 🏗️ Refactor * clean up McpContext getters ([ChromeDevTools#2340](ChromeDevTools#2340)) ([5b33deb](ChromeDevTools@5b33deb)) * clean up more of the context interface ([ChromeDevTools#2335](ChromeDevTools#2335)) ([9cd734b](ChromeDevTools@9cd734b)) * clean up page management ([ChromeDevTools#2333](ChromeDevTools#2333)) ([16db01f](ChromeDevTools@16db01f)) * clean up page snapshot generation ([ChromeDevTools#2348](ChromeDevTools#2348)) ([68cfce2](ChromeDevTools@68cfce2)) * make collectors work per page ([ChromeDevTools#2324](ChromeDevTools#2324)) ([9bc61b4](ChromeDevTools@9bc61b4)) * move and rename files ([ChromeDevTools#2355](ChromeDevTools#2355)) ([9c3542b](ChromeDevTools@9c3542b)) * move DevTools universe to McpPage ([ChromeDevTools#2341](ChromeDevTools#2341)) ([c006c9b](ChromeDevTools@c006c9b)) * move remaining McpContext getters ([ChromeDevTools#2342](ChromeDevTools#2342)) ([58ba174](ChromeDevTools@58ba174)) * remove isolated context getter ([ChromeDevTools#2336](ChromeDevTools#2336)) ([8a4ddb3](ChromeDevTools@8a4ddb3)) * Use array instead of Map for idToClassKey ([ChromeDevTools#2321](ChromeDevTools#2321)) ([ff53b7b](ChromeDevTools@ff53b7b)) * use helper for Dialog handle ([ChromeDevTools#2334](ChromeDevTools#2334)) ([64005f9](ChromeDevTools@64005f9)) * use response page in formatting ([ChromeDevTools#2349](ChromeDevTools#2349)) ([c53c1ec](ChromeDevTools@c53c1ec)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
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.
Follow-up to #2304.
createPagesSnapshot()reselects#pages[0]whenever the selected page is missing from#pages, even when that page is still open. So a live selection can be silently swapped, not only a closed one.The case that hit us is a
devtools://page:handleDevToolsAsPage: true(src/browser.ts), so DevTools frontends are pages and land in#mcpPages#pagesfiltersdevtools://out (src/McpContext.ts, unlessexperimentalDevToolsDebuggingis set)getPageById()(used byselect_page) reads#mcpPages, not#pages, so a devtools page can be selected even thoughlist_pagesnever lists it#pagesand, because the fallback keys on#pagesmembership rather thanisClosed(), reselects#pages[0]with the page still open (isClosed() === false)This reproduces on a normal Chrome (details and a repro in #2304).
This change gates the fallback on
isClosed(), as proposed in #2304. A still-open selection is kept; a genuinely closed page still auto-advances, so the browser-like behavior for closed tabs is unchanged.One consequence: the "is no longer listed" wording added in #2308 becomes unreachable, since a live page missing from the list no longer triggers a fallback. I left it in place to keep this diff focused, but I'm happy to simplify it here or in a follow-up.
The unit test that covered the missing-but-open case now asserts the selection is retained.
Refs: #2304