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

Function ensureServer

browse/src/cli.ts:404–502  ·  view source on GitHub ↗
(flags?: GlobalFlags)

Source from the content-addressed store, hash-verified

402}
403
404async function ensureServer(flags?: GlobalFlags): Promise<ServerState> {
405 const state = readState();
406 const desiredHash = flags?.configHash;
407 const extraEnv: Record<string, string> = {};
408 if (flags?.proxyUrl) extraEnv.BROWSE_PROXY_URL = flags.proxyUrl;
409 if (flags?.headed) extraEnv.BROWSE_HEADED = '1';
410 if (desiredHash) extraEnv.BROWSE_CONFIG_HASH = desiredHash;
411
412 // Health-check-first: HTTP is definitive proof the server is alive and responsive.
413 // This replaces the PID-gated approach which breaks on Windows (Bun's process.kill
414 // always throws ESRCH for Windows PIDs in compiled binaries).
415 if (state && await isServerHealthy(state.port)) {
416 // D2 daemon-mismatch check: existing daemon's configHash must match the
417 // CLI's resolved hash. If --proxy or --headed are passed and the existing
418 // daemon was started with different config, refuse with a `disconnect`
419 // hint. No silent restart — that would drop tab state, cookies, and
420 // logged-in sessions without warning.
421 if (desiredHash && state.configHash && state.configHash !== desiredHash) {
422 console.error(`[browse] existing daemon has different config (proxy/headed mismatch).`);
423 console.error(`[browse] run 'browse disconnect' first to apply --proxy/--headed.`);
424 process.exit(1);
425 }
426 // Same path: existing daemon is plain (no flags) but caller passes
427 // --proxy/--headed. Refuse for the same reason — apply explicitly via
428 // disconnect+reconnect.
429 if (desiredHash && !state.configHash && (flags?.proxyUrl || flags?.headed)) {
430 console.error(`[browse] existing daemon was started without --proxy/--headed.`);
431 console.error(`[browse] run 'browse disconnect' first to apply new flags.`);
432 process.exit(1);
433 }
434
435 // Check for binary version mismatch (auto-restart on update)
436 const currentVersion = readVersionHash();
437 if (currentVersion && state.binaryVersion && currentVersion !== state.binaryVersion) {
438 console.error('[browse] Binary updated, restarting server...');
439 await killServer(state.pid);
440 return startServer(extraEnv);
441 }
442 return state;
443 }
444
445 // BROWSE_NO_AUTOSTART: sidebar agent sets this so the child claude never
446 // spawns an invisible headless browser. If the headed server is down,
447 // fail fast with a clear error instead of silently starting a new one.
448 if (process.env.BROWSE_NO_AUTOSTART === '1') {
449 console.error('[browse] Server not available and BROWSE_NO_AUTOSTART is set.');
450 console.error('[browse] The headed browser may have been closed. Run /open-gstack-browser to restart.');
451 process.exit(1);
452 }
453
454 // Guard: never silently replace a headed server with a headless one.
455 // Headed mode means a user-visible Chrome window is (or was) controlled.
456 // Silently replacing it would be confusing — tell the user to reconnect.
457 if (state && state.mode === 'headed' && isProcessAlive(state.pid)) {
458 console.error(`[browse] Headed server running (PID ${state.pid}) but not responding.`);
459 console.error(`[browse] Run '/open-gstack-browser' to restart.`);
460 process.exit(1);
461 }

Callers 1

mainFunction · 0.85

Calls 9

readVersionHashFunction · 0.90
isProcessAliveFunction · 0.90
ensureStateDirFunction · 0.90
isServerHealthyFunction · 0.85
killServerFunction · 0.85
startServerFunction · 0.85
acquireServerLockFunction · 0.85
releaseLockFunction · 0.85
readStateFunction · 0.70

Tested by

no test coverage detected