fix(browse): daemonize macOS/Linux server via setsid() so it survives sandboxed-shell harnesses#1612
Open
bharat2913 wants to merge 1 commit into
Open
Conversation
`Bun.spawn().unref()` only releases the child from Bun's event loop —
it does NOT call setsid(). The spawned bun server inherits the spawning
shell's process session. When the CLI runs inside a session-managed shell
that exits shortly after the CLI returns (Claude Code's per-command Bash
sandbox, Conductor, OpenClaw, CI step runners), the session leader's exit
sends SIGHUP to every PID in the session — killing the bun server and
its Chromium grandchildren within seconds of a successful `connect`.
Setting `BROWSE_PARENT_PID=0` (already done by the `connect` command and
pair-agent) disables the parent-process watchdog but does NOT save the
server here: SIGHUP from session teardown still reaps it.
Replace the macOS/Linux `Bun.spawn().unref()` with Node's
`child_process.spawn({ detached: true })`, which calls setsid() and
gives the server its own session leader role (PPID=1, STAT=Ss). This
mirrors the Windows path's rationale (PR garrytan#191 by @fqueiro) — same root
cause, different OS surface.
Verified on macOS in Conductor: pre-fix the server dies ~10–15s after
connect across separate Bash invocations; post-fix the same PID stays
alive (PPID=1, SESS=0, STAT=Ss) and responds to `status`/`goto`/
`snapshot` across many separate shell calls.
The `proc?.stderr` startup-error branch is removed since both platforms
now spawn with `stdio: 'ignore'`; both fall through to the on-disk
`browse-startup-error.log` written by `server.ts`'s start().catch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On macOS/Linux,
startServer()spawns the bun server withBun.spawn().unref().unref()only releases the child from Bun's event loop — it does not callsetsid(). The spawned server inherits the spawning shell's process session, so when the CLI runs inside a session-managed shell that exits shortly after the CLI returns (Claude Code's per-command Bash sandbox, Conductor, OpenClaw, many CI step runners), the session leader's exit sendsSIGHUPto every PID in the session — killing the bun server and its Chromium grandchildren within ~10–15s of a successfulconnect.Setting
BROWSE_PARENT_PID=0(already done byconnectand the pair-agent path) disables the in-server watchdog but does not save the server here:SIGHUPfrom session teardown still reaps it.This PR replaces the macOS/Linux
Bun.spawn().unref()with Node'schild_process.spawn({ detached: true }), which callssetsid()and makes the server its own session leader (PPID=1,STAT=Ss). Same rationale as the Windows path (PR #191 by @fqueiro) — same root cause, different OS surface.The
proc?.stderrstartup-error branch is removed since both platforms now spawn withstdio: 'ignore'; both fall through to the on-diskbrowse-startup-error.logwritten byserver.ts'sstart().catch.Repro (without the fix)
Verification (with the fix)
macOS 14 / arm64, in Conductor (Claude Code's per-command Bash sandbox):
Pre-fix, the same flow produces
Mode: launched(fresh headless server) on the next$B statusbecause the headed server has already been reaped.Notes
unref()already detached enough for normal shells, and Node'sdetached:trueis at least as detached on those).os.setsid()via a Python double-fork survives the same Conductor session teardown — so this is a session-attachment issue, not a wholesale "kill all spawned PIDs" sweep. The fix is the standard POSIX daemonization.