Skip to content

Long-lived server can retain active page indefinitely and keep consuming CPU after client activity stops #2599

Description

@paulcakeface

Summary

On a long-lived self-hosted chrome-devtools-mcp 1.7.0 deployment, a page left open after an agent finishes can continue consuming substantial CPU indefinitely even though there are no MCP tool calls in flight.

This appears distinct from the 1.6.0 memory/source-map issues in #2431 / #2456, which are marked fixed in 1.7.0. The problem here is lifecycle: an active page remains live forever unless the client explicitly navigates it away/closes it or the server/browser process is recycled.

Environment

  • chrome-devtools-mcp 1.7.0
  • Linux x86_64
  • Headless Chrome launched by the MCP server
  • Persistent, long-lived MCP gateway/server
  • Docker container with a 2 GiB memory limit
  • Real-world, script-heavy SPA as the target

Observed behaviour

A stale page left by a completed investigation held roughly 28–30% CPU continuously for hours on a 4-core host.

Without restarting the container or MCP gateway, simply closing/navigating the stale page away and leaving only about:blank dropped container CPU to roughly 0.03%, then effectively 0%.

The relevant Chrome launch configuration disables background throttling for debugging accuracy, so a page that remains open can legitimately keep timers/rendering/network activity alive in the background. That behaviour is useful while an investigation is active, but there is currently no lifecycle boundary once the client disappears or stops making calls.

Why this is operationally awkward

For a persistent remote MCP service, persistence currently means browser/page state can survive indefinitely. If a client vanishes without cleanup, the server has no way to distinguish "active investigation" from "abandoned live page".

This is particularly noticeable on dynamic SPAs where background timers, polling, rendering or other work continue after the agent is gone.

Production workaround tested successfully

I added an external gateway lifecycle around the upstream 1.7.0 server with two idle stages:

  1. Park after a shorter idle period

    • retain one page
    • close other stale pages
    • select the retained page
    • navigate it to about:blank
  2. Recycle after a longer idle period

    • stop and recreate only the internal chrome-devtools-mcp child
    • keep the public gateway/container alive
    • release retained V8/performance/browser state

The production thresholds I tested were:

  • park: 20 minutes idle
  • recycle: 60 minutes idle
  • lifecycle check: 10 seconds

Long-running manual operations such as a performance trace or screencast hold an activity lease so lifecycle maintenance does not interrupt legitimate work.

Acceptance evidence from the lifecycle implementation

In isolated testing:

  • a live test page was automatically parked to about:blank
  • post-park CPU fell to about 0.13%
  • a manual performance-trace lease prevented both park and recycle while the trace was active
  • after the trace stopped, one park and one internal recycle occurred
  • child PID changed while the Docker container remained running
  • working set fell from roughly 327 MiB to 139 MiB
  • CPU fell to about 0.02%
  • Docker restart count remained zero
  • the MCP tool surface still worked after recycle

In production, the same approach removed a stale-page case that had been burning roughly 28–30% CPU for hours.

Suggested upstream direction

Would you be open to an upstream idle lifecycle for persistent server usage, for example:

  • track external MCP tool activity
  • park stale pages after configurable idle time
  • optionally recycle/recreate internal browser/server state after a longer idle period
  • protect manual traces/screencasts or other long-running operations with an explicit activity lease

I have a tested implementation in a persistent gateway wrapper and can adapt the concept into an upstream PR if this is a direction the project would accept.

I am deliberately filing this separately from the previous memory issues because 1.7.0 fixed those specific regressions, while this stale-page CPU/lifecycle behaviour is still reproducible on 1.7.0.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions