From 30334e7e6fd2efa869da6b52a2bca7328fa017c1 Mon Sep 17 00:00:00 2001 From: Bharat Date: Wed, 20 May 2026 03:28:55 +0530 Subject: [PATCH] fix(browse): daemonize macOS/Linux server via setsid() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 #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. --- browse/src/cli.ts | 53 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 4f523bea7a..86c9273c90 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -11,6 +11,7 @@ import * as fs from 'fs'; import * as path from 'path'; +import { spawn as nodeSpawn } from 'child_process'; import { safeUnlink, safeUnlinkQuiet, safeKill, isProcessAlive } from './error-handling'; import { writeSecureFile, mkdirSecure } from './file-permissions'; import { resolveConfig, ensureStateDir, readVersionHash } from './config'; @@ -217,8 +218,6 @@ async function startServer(extraEnv?: Record): Promise): Promise): Promise