From 1bc02585aaa6718393c133126e40bbb2e3c15bb8 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 2 Apr 2026 19:32:17 -0700 Subject: [PATCH] feat: BROWSE_NO_AUTOSTART prevents sidebar from spawning headless browser When set, the browse CLI refuses to start a new server and exits with a clear error: "Server not available, run /open-gstack-browser to restart." The sidebar agent sets this so users never get an invisible headless browser when the headed one is closed. --- browse/src/cli.ts | 11 ++++++++++- browse/src/sidebar-agent.ts | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/browse/src/cli.ts b/browse/src/cli.ts index 29409c4a..6e0d42f9 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -330,12 +330,21 @@ async function ensureServer(): Promise { return state; } + // BROWSE_NO_AUTOSTART: sidebar agent sets this so the child claude never + // spawns an invisible headless browser. If the headed server is down, + // fail fast with a clear error instead of silently starting a new one. + if (process.env.BROWSE_NO_AUTOSTART === '1') { + console.error('[browse] Server not available and BROWSE_NO_AUTOSTART is set.'); + console.error('[browse] The headed browser may have been closed. Run /open-gstack-browser to restart.'); + process.exit(1); + } + // Guard: never silently replace a headed server with a headless one. // Headed mode means a user-visible Chrome window is (or was) controlled. // Silently replacing it would be confusing — tell the user to reconnect. if (state && state.mode === 'headed' && isProcessAlive(state.pid)) { console.error(`[browse] Headed server running (PID ${state.pid}) but not responding.`); - console.error(`[browse] Run '$B connect' to restart.`); + console.error(`[browse] Run '/open-gstack-browser' to restart.`); process.exit(1); } diff --git a/browse/src/sidebar-agent.ts b/browse/src/sidebar-agent.ts index f0d62dcd..22f0f53b 100644 --- a/browse/src/sidebar-agent.ts +++ b/browse/src/sidebar-agent.ts @@ -242,9 +242,12 @@ async function askClaude(queueEntry: any): Promise { env: { ...process.env, BROWSE_STATE_FILE: stateFile || '', - // Connect to the existing headed browse server instead of launching a new one. - // Without this, the child claude starts a headless browser on a random port. + // Connect to the existing headed browse server, never start a new one. + // BROWSE_PORT tells the CLI which port to check. + // BROWSE_NO_AUTOSTART prevents spawning an invisible headless browser + // if the headed server is down — fail fast with a clear error instead. BROWSE_PORT: process.env.BROWSE_PORT || '34567', + BROWSE_NO_AUTOSTART: '1', // Pin this agent to its tab — prevents cross-tab interference // when multiple agents run simultaneously BROWSE_TAB: String(tid),