You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Windows] browse fails to start: Chromium disconnects on launch, then resolveDisconnectCause crashes with browser?.process is not a function #2085
Every browse command (goto, --version, --help, status, …) prints:
[browse] Starting server...
[browse] Server failed to start within 15s
and exits 0. The headless browser never becomes usable, so /qa and /qa-only
cannot run on this machine.
Root cause
The 15s timeout is misleading — the server DOES start and bind a port, then the
daemon crashes immediately. Running the server directly surfaces the real error:
$ node dist/server-node.mjs
[browse] Server running on http://127.0.0.1:27493/ (PID: 15352)
[browse] Tab closed (id=1, remaining=0)
[browse] FATAL unhandled rejection: browser?.process is not a function
Secondary (masks the cause + kills the daemon): the disconnect handler resolveDisconnectCause() at src/browser-manager.ts:94 calls browser?.process(). Playwright's Browser has no .process() method, so
this throws an unhandled rejection that crashes the whole server. A recoverable
disconnect becomes a fatal startup failure, and the launcher only reports the
generic "failed to start within 15s".
Not a browser-binary issue
Reproduced identically with three different Chromium binaries:
bundled Playwright chromium-1208
bundled chromium_headless_shell-1208
system Google Chrome via GSTACK_CHROMIUM_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
Same Tab closed → browser?.process is not a function crash every time.
Suggested fix
Guard the .process() call so a disconnect during startup produces a clear,
diagnosable error instead of an unhandled rejection, e.g.:
(Won't restore browsing on its own, but stops the daemon from dying silently
and surfaces the real Windows-launch failure.)
Investigate the immediate Chromium disconnect on Windows/git-bash (#276) — this is the underlying blocker.
Repro
Windows 11 + git-bash, gstack 1.58.4.0
~/.claude/skills/gstack/browse/dist/browse goto https://example.com
Observe "Server failed to start within 15s"
node ~/.claude/skills/gstack/browse/dist/server-node.mjs to see the real crash
Confirming the secondary problem (resolveDisconnectCause → browser?.process()) on macOS — it is not Windows-specific and fires on every headless disconnect, not just launch.
Environment: macOS 26.3.1 arm64, gstack 1.58.4.0 (9fd03fa), Bun 1.3.9, chromium_headless_shell-1208.
Captured 3× with daemon stderr redirected to a file (normal spawns are stdio: 'ignore', so this never surfaces in the field):
[browse] Received SIGTERM (ignoring — use /stop or Ctrl+C for intentional shutdown)
[browse] Tab closed (id=1, remaining=0)
[browse] FATAL unhandled rejection: browser?.process is not a function. (In 'browser?.process()', 'browser?.process' is undefined)
In my case the disconnect was triggered by the CLI's kill+restart path during QA (#2219), but the handler bug is independent of the trigger: Playwright's client Browser (from chromium.launch()) has no .process() method — that's a Puppeteer / Playwright-BrowserServer API. So resolveDisconnectCause (browse/src/browser-manager.ts:94) throws on the first line for every headless disconnect, the intended crash-vs-clean classification never runs, and the intended diagnostic ("Chromium process crashed or was killed" / "closed cleanly") is never printed — the daemon dies as an opaque FATAL unhandled rejection via the module-level handler in server.ts instead.
Consequences beyond Windows startup:
Every disconnect exits via the unhandledRejection path (exit 1) instead of the designed clean/crash exit codes — supervisors reading exit codes (per the comment above resolveDisconnectCause) can't distinguish user-quit from crash.
The masking made a different bug (CLI killing busy daemons) look like spontaneous daemon crashes for weeks of sessions on my machine.
Suggested fix shape: Playwright's client Browser can't provide the child process handle, so the exit-code inspection can't work as written for launch()-mode. Either guard (typeof (browser as any)?.process === 'function') and fall back to a heuristic (e.g. browser.isConnected(), or track an intentionalShutdown flag set by the daemon's own stop path and classify everything else as crash), or launch via chromium.launchServer() + connect() if the process handle is genuinely needed.
Reproduced this end-to-end on another Windows 11 machine (Windows 11 Home 10.0.26200, gstack 1.60.1.0, Bun 1.3.14, chromium_headless_shell-1208) and traced it past the resolveDisconnectCause masking down to the actual Windows disconnect cause, which turned out to be two separate, fixable bugs rather than the sandbox issue from #276.
This alone just stops the daemon from dying opaquely — with it in place, DEBUG=pw:browser on the real disconnect surfaces the actual Chromium stderr instead of swallowing it in an unhandled rejection.
2. Real cause #1: GPU process killed by Chromium's own watchdog
With the guard in place, the disconnect turned out to be a real Chromium-side crash, not a masked non-issue:
[pid=NNNNN][err] ERROR:content\browser\gpu\gpu_process_host.cc:996 GPU process exited unexpectedly: exit_code=143
[pid=NNNNN][err] WARNING:content\browser\gpu\gpu_process_host.cc:1438 The GPU process has crashed 1 time(s)
exit_code=143 happens on a fixed ~14s cadence every launch — that's Chromium's GPU watchdog force-killing an unresponsive GPU process, not resource pressure (18 logical CPUs, 16GB RAM, no other load). --disable-gpu alone does not stop the GPU process from spawning on current Chromium (145.0.7632.6) — the process still spawns and still hangs. --in-process-gpu (runs GPU services inside the browser process instead of a separate watchdog-supervised one) avoids the hang entirely. Verified by launching chrome-headless-shell.exe directly with the exact Playwright arg list plus/minus each flag.
3. Real cause #2: chromium-headless-shell's network service crashes even with GPU fixed
With GPU fixed, the browser survived past 14s but then reliably died 15-30s in with:
[pid=NNNNN][err] ERROR:content\browser\network_service_instance_impl.cc:610 Network service crashed or was terminated, restarting service.
No Windows Error Reporting entry, no Defender detection, no third-party AV/EDR — the network service utility process just doesn't come back. Ruled out a background Cisco AnyConnect VPN service as a red herring (stopping it didn't change anything).
The fix: force Playwright to launch the fullchromium binary instead of chromium-headless-shell. getExecutableName() in chromium.js always picks chromium-headless-shell when headless: true and no channel is set — passing channel: 'chromium' overrides that. Same headless launch, same flags, full binary survives indefinitely; standalone chromium-headless-shell crashes every time within ~30s. Confirmed with a 35s isolated Playwright script (chromium.launch({ headless: true, channel: 'chromium' })) run 3x back to back with zero crashes, vs 3/3 crashes on stock chromium-headless-shell.
Combined patch (browse/src/browser-manager.ts)
// resolveDisconnectCauseconstproc=typeofbrowser?.process==='function' ? browser.process() : null;// BrowserManager.launch(), before the extensionsDir checkif(process.platform==='win32'){launchArgs.push('--disable-gpu','--in-process-gpu');}// chromium.launch(...) call
...(process.platform==='win32' ? {channel: 'chromium'} : {}),
With all three in place, browse goto <url>, text, snapshot, and repeated stop/relaunch cycles ran clean and stable on this machine (previously 0% success rate — every launch crashed within 30s).
One caveat: channel: 'chromium' means Windows users now need the full chromium browser installed, not just chromium-headless-shell (roughly +65MB). Might be worth gating this behind an env var (e.g. GSTACK_CHROMIUM_FULL=1) rather than always-on for win32, in case some Windows environments don't hit the headless-shell network-service crash and would rather keep the smaller download — I don't have another Windows machine to check how widespread this is.
Happy to open a PR with this if useful — let me know if you'd rather gate it behind an env var or just ship it as the win32 default.
Activity
DavidCnoops commentedon Jul 10, 2026
Confirming the secondary problem (
resolveDisconnectCause→browser?.process()) on macOS — it is not Windows-specific and fires on every headless disconnect, not just launch.Environment: macOS 26.3.1 arm64, gstack 1.58.4.0 (
9fd03fa), Bun 1.3.9, chromium_headless_shell-1208.Captured 3× with daemon stderr redirected to a file (normal spawns are
stdio: 'ignore', so this never surfaces in the field):In my case the disconnect was triggered by the CLI's kill+restart path during QA (#2219), but the handler bug is independent of the trigger: Playwright's client
Browser(fromchromium.launch()) has no.process()method — that's a Puppeteer / Playwright-BrowserServerAPI. SoresolveDisconnectCause(browse/src/browser-manager.ts:94) throws on the first line for every headless disconnect, the intended crash-vs-clean classification never runs, and the intended diagnostic ("Chromium process crashed or was killed" / "closed cleanly") is never printed — the daemon dies as an opaqueFATAL unhandled rejectionvia the module-level handler inserver.tsinstead.Consequences beyond Windows startup:
resolveDisconnectCause) can't distinguish user-quit from crash.Suggested fix shape: Playwright's client
Browsercan't provide the child process handle, so the exit-code inspection can't work as written forlaunch()-mode. Either guard (typeof (browser as any)?.process === 'function') and fall back to a heuristic (e.g.browser.isConnected(), or track anintentionalShutdownflag set by the daemon's own stop path and classify everything else as crash), or launch viachromium.launchServer()+connect()if the process handle is genuinely needed.jungkeunmoon commentedon Aug 1, 2026
Reproduced this end-to-end on another Windows 11 machine (Windows 11 Home 10.0.26200, gstack 1.60.1.0, Bun 1.3.14, chromium_headless_shell-1208) and traced it past the
resolveDisconnectCausemasking down to the actual Windows disconnect cause, which turned out to be two separate, fixable bugs rather than the sandbox issue from #276.1.
resolveDisconnectCauseguard (same fix already suggested above)Confirmed the
browser?.process()crash and applied the guard exactly as suggested:This alone just stops the daemon from dying opaquely — with it in place,
DEBUG=pw:browseron the real disconnect surfaces the actual Chromium stderr instead of swallowing it in an unhandled rejection.2. Real cause #1: GPU process killed by Chromium's own watchdog
With the guard in place, the disconnect turned out to be a real Chromium-side crash, not a masked non-issue:
exit_code=143happens on a fixed ~14s cadence every launch — that's Chromium's GPU watchdog force-killing an unresponsive GPU process, not resource pressure (18 logical CPUs, 16GB RAM, no other load).--disable-gpualone does not stop the GPU process from spawning on current Chromium (145.0.7632.6) — the process still spawns and still hangs.--in-process-gpu(runs GPU services inside the browser process instead of a separate watchdog-supervised one) avoids the hang entirely. Verified by launchingchrome-headless-shell.exedirectly with the exact Playwright arg list plus/minus each flag.3. Real cause #2:
chromium-headless-shell's network service crashes even with GPU fixedWith GPU fixed, the browser survived past 14s but then reliably died 15-30s in with:
No Windows Error Reporting entry, no Defender detection, no third-party AV/EDR — the network service utility process just doesn't come back. Ruled out a background Cisco AnyConnect VPN service as a red herring (stopping it didn't change anything).
The fix: force Playwright to launch the full
chromiumbinary instead ofchromium-headless-shell.getExecutableName()inchromium.jsalways pickschromium-headless-shellwhenheadless: trueand nochannelis set — passingchannel: 'chromium'overrides that. Same headless launch, same flags, full binary survives indefinitely; standalonechromium-headless-shellcrashes every time within ~30s. Confirmed with a 35s isolated Playwright script (chromium.launch({ headless: true, channel: 'chromium' })) run 3x back to back with zero crashes, vs 3/3 crashes on stockchromium-headless-shell.Combined patch (
browse/src/browser-manager.ts)With all three in place,
browse goto <url>,text,snapshot, and repeatedstop/relaunch cycles ran clean and stable on this machine (previously 0% success rate — every launch crashed within 30s).One caveat:
channel: 'chromium'means Windows users now need the fullchromiumbrowser installed, not justchromium-headless-shell(roughly +65MB). Might be worth gating this behind an env var (e.g.GSTACK_CHROMIUM_FULL=1) rather than always-on for win32, in case some Windows environments don't hit the headless-shell network-service crash and would rather keep the smaller download — I don't have another Windows machine to check how widespread this is.Happy to open a PR with this if useful — let me know if you'd rather gate it behind an env var or just ship it as the win32 default.
v1.64.0.0 fix wave: full tracker audit — 90 fixes, 52 issues closed, …