ChromeDevTools / ChromeDevTools/chrome-devtools-mcp

ConsoleCollector passes no per-navigation cap to PageCollector, so console messages on one navigation are retained without limit

Open Beginner friendly
#2,768 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
52.3k
Forks
4.3k
Avg merge
2d 7h
Merged PRs (30d)
83

Description

Description of the bug

PageCollector accepts an optional maxResourcesPerNavigation and trims the current navigation's bucket when one is supplied. NetworkCollector passes MAX_REQUESTS_PER_NAVIGATION = 1_000, and ServiceWorkerConsoleCollector keeps its own maxLogs = 1000. ConsoleCollector calls super(page, listeners) with two arguments, so the cap is undefined, the trim branch never runs, and every console message on a navigation is retained for as long as that navigation lasts.

maxNavigationSaved = 3 bounds the number of buckets, not the number of messages in one. A page that does not navigate has exactly one bucket, and it grows without limit. Since #2676 (fix for #2650), same-document navigations deliberately no longer rotate the history. That is the right behaviour for list_console_messages, but it means a single-page app's entire session now lands in that one uncapped bucket.

Cited at main = 882f93e:

The same two-argument call is present in the released 1.9.0 (build/src/collectors/PageCollector.js, super(page, listeners);).

Prior art. #1221 proposed exactly this cap (1000 for ConsoleCollector) in March. It was attached to #1214, which was closed as a duplicate of the --autoConnect leak (#1192), and the PR has not been reviewed. It also targets the pre-move path src/PageCollector.ts. Filing this separately so the console bound is decided on its own merits rather than riding on a closed, unrelated issue.

Reproduction

Run against the installed 1.9.0 package. The shipped ConsoleCollector class is imported unmodified; only the Puppeteer Page and its CDP session are stubbed.

// PKG=<path to an installed chrome-devtools-mcp> node repro.mjs
const {ConsoleCollector} = await import(`${process.env.PKG}/build/src/collectors/PageCollector.js`);
const noop = () => {};
const emitter = () => { const h = {}; return {h, on: (n, f) => { (h[n] ||= []).push(f); }, off: noop, once: noop, emit: (n, ...a) => (h[n]||[]).forEach(f => f(...a))}; };
const session = Object.assign(emitter(), {send: async () => ({}), target: () => ({_targetId: 't1'})});
const page = Object.assign(emitter(), {_client: () => session, mainFrame: () => emitter()});
const c = new ConsoleCollector(page, collect => ({console: collect}));
const N = 50_000;
for (let i = 0; i < N; i++) page.emit('console', {type: () => 'log', text: () => `m${i}`});
console.log(`ConsoleCollector: emitted ${N} console events on one navigation -> retained ${c.getData(false).length}`);

Observed:

ConsoleCollector: emitted 50000 console events on one navigation -> retained 50000

For contrast, the same input driven through PageCollector with and without NetworkCollector's cap:

NetworkCollector.MAX_REQUESTS_PER_NAVIGATION = 1000
pushed 50000 items:
  with a cap (NetworkCollector's shape) -> retained 1000
  no cap      (ConsoleCollector's shape) -> retained 50000
Expectation

Console retention per navigation is bounded, as network requests and service-worker console logs already are.

A possible fix, mirroring NetworkCollector (a named constant plus a defaulted constructor parameter, so a caller can still opt out). No change to PageCollector is needed, because the trim it already implements simply starts running:

export class ConsoleCollector extends PageCollector<…> {
  static readonly MAX_MESSAGES_PER_NAVIGATION = 1_000;

  constructor(
    page: Page,
    listeners: …,
    maxMessagesPerNavigation = ConsoleCollector.MAX_MESSAGES_PER_NAVIGATION,
  ) {
    super(page, listeners, maxMessagesPerNavigation);

Two things you are better placed to judge:

  • Which end to keep. splice from the front drops the oldest messages, which is what the siblings do. Console output is often most useful early in a page's life, where the first errors appear. If that matters here, a head-and-tail retention would serve better. Either bound is an improvement on none.
  • Default or required. Making maxResourcesPerNavigation required on PageCollector would make this class of omission impossible rather than just fixing this instance. It is also a signature change, so it is mentioned rather than proposed.

Scope and honest limits. This is a structural report, not an incident: a buffer fed by page-controlled input with no bound, in a server designed to stay resident. On a long-lived shared deployment where pages stay open for hours, the live instance held 19 console messages when checked, so this has not caused a memory problem there. The concern is any page that logs in a loop or an SPA that is kept open, both of which are ordinary.

MCP configuration

Not relevant to the repro, which exercises the collector class directly. Observed in a long-lived server (one process shared by several MCP clients, headless, --experimentalPageIdRouting).

Chrome DevTools MCP version

1.9.0 (installed); the cited lines are re-read at main 882f93e.

Chrome version

Not involved (the page is stubbed).

Node version

v24.14.1

Operating system

Linux

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/collectors/PageCollector.ts at ConsoleCollector and compare its constructor with NetworkCollector and ServiceWorkerConsoleCollector. Run the supplied 50,000-event reproduction to confirm the uncapped retention, then verify that console messages per navigation are bounded while the existing navigation behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
devtools
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.