Skip to content

fix: skip frozen/discarded targets in page enumeration#1841

Open
20syldev wants to merge 2 commits intoChromeDevTools:mainfrom
20syldev:fix/skip-frozen-discarded-page-targets
Open

fix: skip frozen/discarded targets in page enumeration#1841
20syldev wants to merge 2 commits intoChromeDevTools:mainfrom
20syldev:fix/skip-frozen-discarded-page-targets

Conversation

@20syldev
Copy link
Copy Markdown

@20syldev 20syldev commented Apr 9, 2026

Problem

When Chrome freezes or discards background tabs (memory-saving behavior), browser.pages() can time out on Network.enable for those targets and abort the entire page enumeration — even though the rest of the browser and all active tabs are healthy.

Network.enable timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.

This causes any MCP tool that needs page enumeration (list_pages, take_snapshot, navigation flows, etc.) to fail completely when the connected Chrome profile has been open long enough for background tabs to be frozen/discarded.

Root cause

browser.pages() iterates every page-type target and calls target.page() for each, which creates a Puppeteer CDP session and sends initialization commands including Network.enable. Frozen/discarded targets cannot respond to page-session CDP commands, so they time out — and the error propagates out of browser.pages(), killing the entire list.

Fix

Replace the single browser.pages() call in #getAllPages() with a per-target iteration over browser.targets(). Each call to target.page() is wrapped in its own try/catch so that one unresponsive background tab is logged and skipped instead of aborting the whole enumeration.

  • page type targets are always included (existing behavior)
  • background_page / webview types are included when experimentalIncludeAllPages is set (preserves existing opt-in behavior)
  • The allTargets snapshot is reused for the extension-target loop below, so there is no extra browser.targets() call

Fixes #1230

@google-cla
Copy link
Copy Markdown

google-cla bot commented Apr 9, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@20syldev 20syldev changed the title Skip frozen/discarded targets in page enumeration fix: skip frozen/discarded targets in page enumeration Apr 9, 2026
@20syldev 20syldev closed this Apr 9, 2026
@20syldev 20syldev reopened this Apr 9, 2026
Copy link
Copy Markdown
Collaborator

@OrKoN OrKoN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/McpContext.ts Outdated
@20syldev 20syldev force-pushed the fix/skip-frozen-discarded-page-targets branch 2 times, most recently from 8868f23 to 92aa2fc Compare April 10, 2026 09:11
@20syldev 20syldev requested a review from OrKoN April 10, 2026 09:14
Copy link
Copy Markdown
Collaborator

@OrKoN OrKoN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test demonstrating that it works with actual frozen tabs in the browser and not only in the mock. As I mentioned on #1230 (comment) I have troubles reproducing the issue. Do you have additional details for the manual reproduction?

@20syldev 20syldev force-pushed the fix/skip-frozen-discarded-page-targets branch from 92aa2fc to ae01916 Compare April 10, 2026 12:02
@20syldev
Copy link
Copy Markdown
Author

Please add a test demonstrating that it works with actual frozen tabs in the browser and not only in the mock. As I mentioned on #1230 (comment) I have troubles reproducing the issue. Do you have additional details for the manual reproduction?

How to reproduce

The issue requires a dead rendererPage.setWebLifecycleState("frozen") via CDP is not enough because the renderer still responds to CDP commands. The actual scenario is a tab whose renderer process was killed (discarded by Chrome's memory saver or crashed).

Steps:

  1. Launch Chrome with remote debugging:
    google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
  2. Open a few tabs, then navigate one to chrome://crash (kills the renderer)
  3. Run this script:
    import { connect } from 'puppeteer';
    
    const browser = await connect({ browserURL: 'http://localhost:9222' });
    console.log('Connected, calling browser.pages()...');
    const pages = await browser.pages(); // hangs here for 180s
    console.log(pages.length);
  4. browser.pages()hangs indefinitely (180s protocolTimeout)

This only happens with puppeteer.connect(), not puppeteer.launch(), because launch auto-attaches to all targets and caches Page instances. On connect, target.page() has to initialize from scratch, sending Network.enable to a dead renderer that never responds.

Added test

The new integration test (should enumerate pages when a tab has a crashed renderer) reproduces exactly this:

  • Launches Chrome, creates an active page and a crashed page (chrome://crash)
  • Disconnects and reconnects via WebSocket
  • Creates an McpContext (which calls #getAllPages() internally)
  • Asserts it completes in under 20s and finds the active pages
$ NODE_TEST_REPORTER=spec npm test -- tests/McpContext.test.ts 2>&1 | grep -A2 "crashed renderer"
✔ should enumerate pages when a tab has a crashed renderer (10459.212304ms)

~10s = two #getAllPages() calls during init, each hitting the 5s per-target timeout on the crashed tab. Without the fix, this would be 360s+ (2 × 180s protocolTimeout).

@20syldev 20syldev requested a review from OrKoN April 10, 2026 12:33
Launches Chrome, crashes a tab via chrome://crash, disconnects
and reconnects to verify page enumeration skips the dead target.
@20syldev 20syldev force-pushed the fix/skip-frozen-discarded-page-targets branch from ae01916 to 2667068 Compare April 13, 2026 08:32
Comment thread src/McpContext.ts
const type = target.type();
if (type === 'page') return true;
if (this.#options.experimentalIncludeAllPages) {
return type === 'background_page' || type === 'webview';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the enumeration here sufficient? type: What are the categories in total?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CDP target types are: page, background_page, service_worker, webview, worker, shared_worker, browser, and other.

Here only page is enumerated by default (same as what browser.pages() did before), and background_page + webview are added when experimentalIncludeAllPages is on — that's the pre-existing opt-in. The rest (service_worker, worker, etc.) aren't navigable pages so target.page() wouldn't return anything useful for them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frozen/discarded background tabs can make page enumeration fail with Network.enable timeout

3 participants