Fixed in v1.9.89, shipped before this was filed. (Heads-up: the body of this issue is a raw file reference — @/tmp/.../pup-issue.md — that did not resolve, so the write-up itself did not make it onto the page. The symptoms in the title match exactly what was diagnosed and fixed, so I am answering that; if the file contained findings beyond the CPU/freeze, please paste them inline and I will pick them up.)
Root cause
browser_close / browser_close_window called browser.close() plus a process-tree kill, then swept for renderer children that had been reparented (a crashed or detached renderer survives a tree kill because it is no longer a child of the process we killed). That sweep had two fatal properties:
- Fire-and-forget. The close returned
ok before the sweep finished or failed, so a close that left processes behind still reported success.
- It timed out exactly when it was needed. It enumerated the FULL Windows process table and filtered afterward, with an 8s timeout. At 88-98% CPU — the precise condition the runaway renderers create — that enumeration does not finish in 8s. It gave up silently, and the orphans kept spinning at roughly a full core each, indefinitely.
That is why this could starve dwm (the cursor compositor) badly enough to freeze the mouse and force a reboot, and why James's #14 saw three sessions survive two days.
The fix
- The reap is awaited and verified: close does not return until the processes are confirmed gone.
- Load-tolerant enumeration: the WMI query filters to chrome/msedge server-side, so only candidate rows materialize. It stays fast at 98% CPU instead of timing out.
- Every close response returns
chromeProcsRemaining (should be 0), with a warning hint if anything survives, so a caller can verify instead of trusting.
- Boot + periodic (3 min) + close-all orphan sweeps reap any pup chrome whose profile has no live session, so an orphan from a crash, or one that outlived a close, gets cleaned with no close call needed.
Scoping is by pup's own profile dir, so it can never touch the user's real browser or another agent's window.
Verified with ground truth
Measured via AD's own process_list (independent of pup's self-reporting), on AdomLapper:
- 3 windows spawned 21 processes (6 renderers) → after close, 0 remained; each close reported
chromeProcsRemaining: 0.
- In a second run, closing two windows reaped 17 processes to 0 while another agent thread's window (7 procs) was left untouched — the sparing matters as much as the killing.
A related hazard the fix exposed and closed in v1.9.92: the new orphan sweep decides "orphan" as no live session owns this profile, so an empty session map would have made every live window an orphan. It now refuses to sweep when the session map is empty while session files still exist on disk.
How to get it
Source-only change, so it rides AD's normal bridge auto-update with no dependency blocker: automatic on the next poll (~4h), or immediately with refresh_bridges {"name":"puppeteer"} + restart_bridge {"name":"puppeteer"}. Confirm bridge_list shows puppeteer at 1.9.89 or newer (current is 1.9.94). If orphans are spinning right now, the restart's boot sweep clears them within ~3 minutes.
Emergency workaround if you cannot update yet — matches only pup's own profiles, never your personal Chrome:
Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" |
Where-Object { $_.CommandLine -like '*\.adom\pup-profiles\*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Closing as fixed. Reopen if chromeProcsRemaining ever comes back non-zero, that is the signal.