Two changes, one contract:
- Pre-server short-circuit: `browse stop` is handled BEFORE ensureServer().
No daemon state → "nothing to stop", exit 0. Stale state (dead pid AND
dead port) → clean the state file, exit 0. The old flow routed stop
through ensureServer(), which started a fresh daemon + Chromium
(multi-second boot, resource churn) purely so it could be told to shut
down — or crashed on the stale state.
- Reconnect branch: a connection error while sending `stop` where the pid
turns out dead (daemon died mid-flight, between the short-circuit check
and the send) is treated as SUCCESS — the desired end state (no daemon)
already holds — instead of the crash-restart path.
Integration tests (stop-dead-daemon.test.ts, real spawned CLI + scratch
BROWSE_STATE_FILE): stop with no state exits 0 and spawns nothing (a
spawned daemon would have written the state file); stop with a stale state
file (dead pid + verified-closed port) exits 0, cleans the state, and
spawns nothing.
Tests: stop-dead-daemon 2 pass; busy-daemon-iron-rule 8 pass;
busy-daemon-recovery 1 pass (11/11 combined).
Fixes#2254.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>