From 748cabfe49d4a0e65ffbd8a449d97f2173d85738 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 4 Oct 2026 19:41:21 +0000 Subject: [PATCH] E3: reap the recorded Chromium only after the daemon has exited With the PID now recorded (E3), browse stop reaped Chromium while the daemon was still shutting down; the daemon read that as a crash, exited 1 and left its state file (chromium-profile-isolation's headless stop test failed 3/3 locally, passes on base). The CLI waits up to 5 s for the daemon to exit before reaping a survivor. --- browse/src/cli.ts | 5 +++++ browse/test/headless-gpu-and-reap.test.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 0bc17876e..e7b5b1f91 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -2080,7 +2080,12 @@ Refs: After 'snapshot', use @e1, @e2... as selectors: // Playwright — but on macOS 26 the GPU process can survive that close and // spin at ~800% CPU forever. The state snapshot read above still carries // the launched child's identity; reap a verified survivor. + // Reap only after the daemon has finished its own shutdown: killing Chromium + // while the daemon is still closing reads as a crash, and the daemon exits(1) + // without removing its state file. if (command === 'stop') { + const deadline = Date.now() + 5_000; + while (Date.now() < deadline && isProcessAlive(state.pid)) await Bun.sleep(100); await reapRecordedChromium(state); } diff --git a/browse/test/headless-gpu-and-reap.test.ts b/browse/test/headless-gpu-and-reap.test.ts index 4b06423c1..af12ada21 100644 --- a/browse/test/headless-gpu-and-reap.test.ts +++ b/browse/test/headless-gpu-and-reap.test.ts @@ -254,10 +254,12 @@ describe('stop-reap wiring pins (#2709)', () => { ); // 5. Post-graceful-stop: the daemon closed Chromium via Playwright, but a - // surviving GPU process must still be reaped after sendCommand('stop'). + // surviving GPU process must still be reaped after sendCommand('stop') — + // only once the daemon has exited (an earlier kill reads as a crash and + // the daemon exits without removing its state file). const postStop = between('await sendCommand(state, command, commandArgs);', "if (command === 'focus')"); expect(postStop).toMatch( - /if \(command === 'stop'\) \{\s*\n\s*await reapRecordedChromium\(state\);/, + /if \(command === 'stop'\) \{[\s\S]*while \(.*isProcessAlive\(state\.pid\)\)[^\n]*\n\s*await reapRecordedChromium\(state\);/, ); });