Skip to content

fix(browse): skip crash-retry when user invokes stop or restart - #2128

Closed
TTmo123 wants to merge 2 commits into
garrytan:mainfrom
TTmo123:fix/browse-stop-skip-crash-retry
Closed

TTmo123 wants to merge 2 commits into
garrytan:mainfrom
TTmo123:fix/browse-stop-skip-crash-retry

Conversation

@TTmo123

@TTmo123 TTmo123 commented Jun 28, 2026

Copy link
Copy Markdown

What

browse stop and browse restart no longer trigger the "Server crashed twice in a row — aborting" warning.

Why

Today's sendCommand ECONNRESET handler (browse/src/cli.ts:586) treats every dropped connection as a crash. But stop and restart are user-initiated shutdowns (daemon-side at browse/src/meta-commands.ts:423, :428): the daemon intentionally exits before the HTTP response body flushes. The restart path then sends stop again, the new daemon exits again, and retries >= 1 throws the misleading crash message.

How

One 4-line branch at the top of the ECONNRESET handler:

if (command === 'stop' || command === 'restart') {
  process.exit(0);
}

command is already in closure from sendCommand's function signature (cli.ts:527) — no new param passing. process.exit(0) (not a bare throw) is intentional: main()'s .catch() (cli.ts:1357-1362) would print [browse] fetch failed and exit 1 on a thrown error. Clean exit 0 is what the user intended.

Verification

  • bun test test/send-command-stop-skip-restart.test.ts — 4 new cases pass (2 integration + 2 regression-guard)
  • bun test — full Tier 1 suite passes
  • bun build browse/src/cli.ts — TypeScript compiles cleanly
  • bun run test:e2e (Tier 2) — requires EVALS secret, maintainer-side gate
  • Manual: browse stop — exits 0, no stderr output
  • Manual regression: kill the daemon externally, then browse goto <url> — crash-retry still fires (unchanged)

Notes for reviewer

  • Independent of PR #NNNN (PR 1, setup symlink). Can be reviewed and merged separately.
  • Daemon-side stop/restart handlers (meta-commands.ts:423-433) are unchanged. The "Server stopped" / "Restarting..." strings rarely reach the client because shutdown() exits before the response body flushes; the fix respects that as an intentional daemon behavior.
  • No chore: rebuild commit: .gitignore excludes browse/dist/, and CI's windows-setup-e2e.yml rebuilds binaries on every push touching browse/src/cli.ts (trigger path at .github/workflows/windows-setup-e2e.yml:22).
  • Test design: cases 1-2 are integration tests gated by describe.skipIf(!HAS_BINARY) so shards without a built binary gracefully skip; cases 3-4 are code-shape regression guards that always run, catching future refactors that accidentally widen the short-circuit or strip the crash-retry path.

TTmo123 and others added 2 commits June 28, 2026 22:01
sendCommand's ECONNRESET handler treats every dropped connection as a
crash and restarts the daemon. But stop and restart are user-initiated
shutdowns: the daemon intentionally closes the HTTP connection before
the response body flushes (meta-commands.ts:423-433). The restart
attempt sends stop again, the new daemon exits again, and retries >= 1
throws 'Server crashed twice in a row — aborting.'

Fix: add a short-circuit check for command === 'stop' || command ===
'restart' at the top of the ECONNRESET handler. When user-intended,
call process.exit(0) to skip main()'s surrounding .catch(), which
would print '[browse] fetch failed' and exit 1.

'command' is already in closure from sendCommand's signature (cli.ts:527).
The recursive calls at cli.ts:596 and :613 pass 'command' explicitly.

Daemon-side handlers (meta-commands.ts) are unchanged. No 'chore:
rebuild' commit — CI's windows-setup-e2e.yml rebuilds browse binary
on every push touching browse/src/cli.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mands

Four test cases for the sendCommand ECONNRESET fix:

1. stop command on ECONNRESET does not print crash warning — runs
   browse stop against a real daemon; asserts exit 0 and absence of
   'crashed' / 'aborting' / 'fetch failed' in stderr.
2. restart command on ECONNRESET does not print crash warning — same
   assertion for the restart command.
3. guard is scoped to stop + restart only (regression guard) — reads
   browse/src/cli.ts and asserts the new short-circuit checks both
   'stop' and 'restart', and that the original ECONNREFUSED / ECONNRESET
   handler is preserved.
4. guard preserves original crash-retry path (regression guard) —
   asserts 'Server crashed twice in a row' and the 'retries >= 1'
   guard are still present.

Cases 1-2 use describe.skipIf(!HAS_BINARY) so shards without a
built binary gracefully skip. Cases 3-4 always run as source-shape
checks, providing a safety net against future refactors that
accidentally widen the short-circuit or strip the crash-retry path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jun 28, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@time-attack

Copy link
Copy Markdown
Contributor

Just reports succesful whenevr the connection fails. Even if the daemon never recieved the stop command
close

@time-attack

Copy link
Copy Markdown
Contributor

@16francej — recommend closing this PR because it can report a successful stop or restart without proving the daemon processed the command. The available coverage checks only the client exit behavior, not handler execution or resulting process state, so it does not safely resolve #1732.

@capy-ai

capy-ai Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for this, @TTmo123. The fix is now on main: stop is acknowledged before the daemon exits, a stop against a dead daemon exits cleanly, and stop short-circuits when no daemon runs (browse/src/cli.ts, meta-commands.ts) (fixed in v1.65.0.0, #2577 and v1.67.0.0, #2604). Closing as superseded; if you still see the problem on the latest gstack, please open an issue with ./setup --status output.

@capy-ai capy-ai Bot closed this Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants