MCPcopy
hub / github.com/garrytan/gstack / startServer

Function startServer

browse/src/cli.ts:295–373  ·  view source on GitHub ↗
(extraEnv?: Record<string, string>)

Source from the content-addressed store, hash-verified

293
294// ─── Server Lifecycle ──────────────────────────────────────────
295async function startServer(extraEnv?: Record<string, string>): Promise<ServerState> {
296 ensureStateDir(config);
297
298 // Clean up stale state file and error log
299 safeUnlink(config.stateFile);
300 safeUnlink(path.join(config.stateDir, 'browse-startup-error.log'));
301
302 // #1781: clear a stale Chromium profile lock (and kill the orphan still
303 // holding it) before launch, so an auto-restart after an abrupt kill isn't
304 // blocked by the previous Chromium's SingletonLock — the self-inflicted
305 // crash-loop. Previously only the manual connect preamble did this.
306 await killOrphanChromium();
307 cleanChromiumProfileLocks();
308
309 // Allow the caller to opt out of the parent-process watchdog by setting
310 // BROWSE_PARENT_PID=0 in the environment. Useful for CI, non-interactive
311 // shells, and short-lived Bash invocations that need the server to outlive
312 // the spawning CLI. Defaults to the current process PID (watchdog active).
313 // Parse as int so stray whitespace ("0\n") still opts out — matches the
314 // server's own parseInt at server.ts:760.
315 const parentPid = parseInt(process.env.BROWSE_PARENT_PID || '', 10) === 0 ? '0' : String(process.pid);
316
317 if (IS_WINDOWS && NODE_SERVER_SCRIPT) {
318 // Windows: Bun.spawn() + proc.unref() doesn't truly detach on Windows —
319 // when the CLI exits, the server dies with it. Use Node's child_process.spawn
320 // with { detached: true } instead, which is the gold standard for Windows
321 // process independence. Credit: PR #191 by @fqueiro.
322 const extraEnvStr = JSON.stringify({ BROWSE_STATE_FILE: config.stateFile, BROWSE_PARENT_PID: parentPid, ...(extraEnv || {}) });
323 const launcherCode =
324 `const{spawn}=require('child_process');` +
325 `spawn(process.execPath,[${JSON.stringify(NODE_SERVER_SCRIPT)}],` +
326 `{detached:true,stdio:['ignore','ignore','ignore'],env:Object.assign({},process.env,` +
327 `${extraEnvStr})}).unref()`;
328 Bun.spawnSync(['node', '-e', launcherCode], { stdio: ['ignore', 'ignore', 'ignore'] });
329 } else {
330 // macOS/Linux: Bun.spawn().unref() only removes the child from Bun's event
331 // loop — it does NOT call setsid(), so the spawned server stays in the
332 // parent's process session. When the CLI runs inside a session-managed
333 // shell (e.g. Claude Code's per-command Bash sandbox, Conductor, CI
334 // step runners), the session leader's exit sends SIGHUP to every PID in
335 // the session, killing the bun server (and its Chromium grandchildren).
336 // Even with BROWSE_PARENT_PID=0 disabling the watchdog, SIGHUP still
337 // reaps the server. Use Node's child_process.spawn with detached:true,
338 // which calls setsid() so the server becomes its own session leader
339 // (PPID=1, STAT=Ss) and survives the spawning shell's exit. Mirrors
340 // the Windows path's rationale — same root cause, different OS API.
341 nodeSpawn('bun', ['run', SERVER_SCRIPT], {
342 detached: true,
343 stdio: ['ignore', 'ignore', 'ignore'],
344 env: { ...process.env, BROWSE_STATE_FILE: config.stateFile, BROWSE_PARENT_PID: parentPid, ...extraEnv },
345 }).unref();
346 }
347
348 // Wait for server to become healthy.
349 // Use HTTP health check (not isProcessAlive) — it's fast (~instant ECONNREFUSED)
350 // and works reliably on all platforms including Windows.
351 const start = Date.now();
352 while (Date.now() - start < MAX_START_WAIT) {

Callers 3

ensureServerFunction · 0.85
sendCommandFunction · 0.85
mainFunction · 0.85

Calls 6

ensureStateDirFunction · 0.90
safeUnlinkFunction · 0.90
killOrphanChromiumFunction · 0.85
isServerHealthyFunction · 0.85
readStateFunction · 0.70

Tested by

no test coverage detected