Appearance
date: 2026-06-13 tags: [pest-browser, playwright, run-server, orphan, hang, session-start, bug-501] status: active graduated_to:
Browser harness hangs on stale Playwright run-server state — now reaped, not just warned about
Symptom — composer test:browser (or php artisan test tests/Browser/…) hangs indefinitely with zero output, not every time but "all the time" in practice. A hung run is killed by timeout, which leaves state that makes the next run hang too, so it compounds into "the harness is broken." A known-good, already-committed test (DashboardSmokeTest) reproduced it; from clean state the same five smoke tests pass together in ~3.8s.
Root cause — pest-plugin-browser leaves two pieces of stale state behind when a run is interrupted (and sometimes even when it completes): an orphaned node … playwright run-server --mode launchServer process, and a stale endpoint file at vendor/pestphp/pest-plugin-browser/.temp/playwright-server.json (written by AlreadyStartedPlaywrightServer::persist(), re-read by fromPersisted()). The next run wedges on the leftover — PlaywrightNpmServer::start() blocks forever on its waitUntil('Listening on'), and the orphan's inherited file descriptors also block any command-substitution wrapping the run. Chromium itself is fine (148.x at /opt/pw-browsers, all libs present, revision matches Playwright 1.60.0). [[2026-06-12-pest-browser-works-in-web-container]] flagged the orphan as a "residual trap … kill it before retrying" but never guarded it — this is the guard.
The self-kill footgun — the obvious reap, pkill -f "playwright run-server", matches its own shell command line and kills its parent, orphaning the very server it meant to clean up. Use the [p] character class so the pattern can't match itself:
ps -eo pid,command | awk '/[p]laywright run-server/{print $1}' | xargs -r kill -9Fix — php artisan browser:reap (app/Console/Commands/ReapBrowserServers.php) deletes the state file and force-kills any stray run-server, OS-aware (the awk pipeline on unix, a Stop-Process PowerShell one-liner on Windows — best-effort, never failing). It's wired as the first step of composer test:browser and into .claude/hooks/session-start.sh, so local, CI and web-container/autonomous runs all start clean.
Guard — the reaper runs before every browser session automatically. When debugging a browser hang manually: reap first (the [p] pipeline + rm -f vendor/pestphp/pest-plugin-browser/.temp/playwright-server.json), and never pkill -f "playwright run-server" (self-kill). Bug #501.