ChromeDevTools / ChromeDevTools/chrome-devtools-mcp
Isolated/temp-profile Chrome survives MCP death on macOS (puppeteer_dev_chrome_profile orphans, ppid=1)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 52.3k
- Forks
- 4.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 83
Description
Summary
On macOS, Chrome instances launched by chrome-devtools-mcp via Puppeteer's temporary profile (puppeteer_dev_chrome_profile-*) keep running after the MCP server process is gone. They reparent to launchd (ppid=1) and accumulate for hours.
This is not "please add a close-browser MCP tool" (#484) and not "idle page CPU in connected mode" (#2599). It is also distinct from #1765, where SIGTERM of a still-alive MCP server does clean up Chrome. The hole is: MCP dies without a graceful SIGTERM (crash, SIGKILL, host killing the agent), and the isolated Chrome tree is not in a process group / death-pipe that dies with it.
Environment
- macOS 26 (Darwin arm64, Apple Silicon)
chrome-devtools-mcp@1.8.0(vianpx)- Chrome Stable 151.0.7922.174 (
/Applications/Google Chrome.app) for the orphaned trees - MCP clients on the same machine: Codex, Grok CLI, Cursor (each session starts a stdio MCP server)
- Some clients attach with
--browserUrl=http://127.0.0.1:9333to a shared Chrome for Testing. The leaked trees are not that shared instance.
Fingerprint of the leaked processes
Each orphan is a full headless Chrome root (plus ~6 helpers):
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
--headless=new
--enable-automation
--user-data-dir=/var/folders/.../T/puppeteer_dev_chrome_profile-<rand>
--remote-debugging-port=0
ppid=1 # launchd
That user-data-dir name is Puppeteer's mkdtemp('puppeteer_dev_chrome_profile-') when userDataDir is left unset — i.e. --isolated / temp-profile launch in src/browser.ts, not the persistent ~/.cache/chrome-devtools-mcp/chrome-profile and not --browserUrl attach.
Measured on one workstation (2026-08-26)
- 42 orphan Chrome roots matching the fingerprint above (plus ~300 helper processes).
- Ages from ~5 minutes to 9+ hours.
- No living Node/Puppeteer parent for any of them.
- The shared
--browserUrlChrome for Testing on:9333was healthy and not part of this set. chrome-devtools-mcpstdio servers for live agent sessions were still parented correctly; the leaked Chromes were from sessions that had already died.- MCP attach count that day (shared launcher log): 83
connecting mcplines. The local reaper only ran when the shared:9333Chrome itself restarted — so once that shared instance stays up, isolated leftovers are never reclaimed.
Killing only those 42 roots (puppeteer_dev_chrome_profile-*, --type= helpers excluded) left the shared :9333 instance and agent-browser Chrome for Testing untouched.
Expected
If the MCP server process exits for any reason — SIGTERM, SIGKILL, crash, stdin close — Chrome launched in isolated/temp-profile mode should die with it (main + GPU/renderer/utility/crashpad), and the temp profile dir should be removed.
Actual
Chrome is detached. After the MCP process disappears, macOS reparents the browser to launchd. --headless=new often does not exit on its own (Chromium 327583144). Trees sit until a human kills them.
Why this is on chrome-devtools-mcp (not only the client)
#1765 / gemini-cli#13391 correctly say: clients should SIGTERM the MCP server. That is necessary and not sufficient:
- Agent hosts routinely SIGKILL workers (timeouts, IDE/terminal kill, process replacement). SIGTERM handlers never run.
- Puppeteer launches Chrome as a detached process group. SIGKILL of Node does not cascade.
--isolatedis the recommended way to run multiple MCP clients in parallel (the error even says "Use--isolatedto run multiple browser instances"). Parallel Codex/Grok/Cursor sessions are the default in 2026, so isolated temp profiles are the common path — and each one can leak a full Chrome tree.
Related reports with the same fingerprint:
- kirodotdev/Kiro#8152 —
ps | grep puppeteer_dev_chrome_profileafter subagent end; MCP PPID=1 - openclaw/openclaw#85721 —
chrome-devtools-mcpprocesses accumulate,client.close()does not reap the npx tree - NousResearch/hermes-agent#29363 — subprocess leak across session/gateway restart
Suggested fix (server-side, SIGKILL-safe)
Do not rely on process.on('SIGTERM') + browser.close() for isolated Chrome. That path is already wired (closeBrowser() on SIGTERM/SIGINT/SIGHUP/stdin end) and still loses the tree when the process is SIGKILL'd.
What works in similar tools (vercel-labs/agent-browser#1148 death-pipe sentinel):
- Launch isolated Chrome in its own process group.
- Hold a pipe/socket in the MCP process; a tiny sentinel blocks on the read end and
kill(-pgid, SIGKILL)when the pipe EOF's (covers SIGKILL of Node). - Default idle timeout for isolated/temp-profile browsers (e.g. 1h).
agent-browser0.34 now does this; it was the only reliable reclaim when clients die uncleanly (vercel-labs/agent-browser#1536). - On next MCP startup, reap leftover
puppeteer_dev_chrome_profile-*Chrome roots whose parent is gone.
--browserUrl attach should not kill the remote browser. Isolated/temp-profile launch should.
Workaround we are using
External reaper (LaunchAgent every 5 min) that SIGTERM/SIGKILLs only:
ps -axo pid=,command= | awk '/puppeteer_dev_chrome_profile-/ && /Google Chrome/ && !/--type=/'
That does not touch --browserUrl shared Chrome or the user's interactive Chrome.app. It should not be required.
Happy to provide a full ps dump or a minimal repro script (spawn MCP with --isolated --headless, kill -9 the Node PID, watch Chrome stay on ppid=1).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/browser.ts and trace the isolated/temp-profile launch and existing closeBrowser handlers. Reproduce with an isolated headless server, kill the Node process, and inspect the resulting Chrome tree and temporary profile. Done means isolated Chrome processes and profiles are reclaimed after unclean exit while --browserUrl attached browsers remain untouched.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- devtools, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100