Skip to content

browse: daemon's SIGTERM "ignore" is cosmetic — Playwright's default handleSIGTERM closes Chromium anyway #2220

Closed

Description

@DavidCnoops

Environment

  • gstack: 1.58.4.0 (9fd03fa)
  • OS: macOS 26.3.1 (arm64), Bun 1.3.9
  • Playwright: chromium_headless_shell-1208

Symptom

The daemon advertises that it ignores SIGTERM:

[browse] Received SIGTERM (ignoring — use /stop or Ctrl+C for intentional shutdown)

…but ~1s after receiving SIGTERM it loses its browser and exits anyway:

[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. (...)

Captured 3× on macOS with daemon stderr redirected to a file (normal spawns are stdio: 'ignore', so this is invisible in the field).

Root cause

chromium.launch() at browse/src/browser-manager.ts:372 does not set the handleSIGINT / handleSIGTERM / handleSIGHUP launch options. Playwright's defaults are true, so Playwright installs its own process-level signal listeners that close the launched browser when the daemon process receives the signal — completely independent of the daemon's own "ignore" handler in server.ts.

Net effect: the app-level SIGTERM handler runs and logs "ignoring", Playwright's handler closes Chromium anyway, the tab closes, browser.disconnected fires, and handleChromiumDisconnect exits the process (currently via the browser?.process() TypeError — #2085 secondary — but it would process.exit() by design even without that bug). The "ignore" is cosmetic.

This matters in practice because killServer() (browse/src/cli.ts:157) leads with SIGTERM: any CLI-initiated restart tears down Chromium through Playwright's hook before the SIGKILL fallback ever fires, so the intended graceful-shutdown/ignore semantics never get a chance to apply. It also means any environment that sprays SIGTERM (session teardown, supervisors) kills the browser even though the daemon deliberately opted out.

Repro

  1. BROWSE_PARENT_PID=0 bun run browse/src/server.ts 2>&1 | tee /tmp/browse.log, wait for healthy, browse goto about:blank.
  2. kill -TERM <daemon pid>.
  3. Log shows "ignoring", then the tab closes and the daemon exits ~1–2s later.

Suggested fix

Pass handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false to chromium.launch() (and the launchPersistentContext call at browse/src/browser-manager.ts:579), and let the daemon's own signal handlers own the shutdown policy — they already exist and already encode the intended behavior.

Related: the kill-happy CLI path that sends these SIGTERMs during normal QA is filed separately as #2219.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions