Skip to content

feat: reins restart, and a stale daemon restarts itself after an upgrade - #39

Merged
karngyan merged 3 commits into
mainfrom
feat/reins-restart
Sep 26, 2026
Merged

karngyan merged 3 commits into
mainfrom
feat/reins-restart

Conversation

@karngyan

@karngyan karngyan commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Why

After npm i -g @karnstack/reins@latest, the background daemon kept running the old code. /health already reports the daemon's version, but nothing compared it with the CLI's, and there was no restart command, only reins kill plus knowing that the daemon respawns on demand.

What

  • Restart on upgrade: the next tool command (reins tabs etc., plus policy and extension --reload) restarts a daemon older than the CLI and prints reins: daemon runs v0.4.0, CLI is v0.5.0 — restarting it to stderr.
    • A newer daemon is left alone, so two installed CLI versions can't restart each other back and forth.
    • 0.0.0 (unreadable package.json) never triggers a restart.
    • If the old daemon won't stop, the command warns and keeps using it instead of failing.
  • reins restart: stops the daemon, waits for its port to go quiet, spawns a fresh one, and waits up to 15s for previously connected browsers to come back. It exits 1 if they don't:
    daemon restarted on 127.0.0.1:8766 (v0.4.0 → v0.5.0)
    browser: 1 connected (Google Chrome)
    
  • reins kill: now waits until the daemon has actually exited. A rejected /shutdown counts as "already stopping".
  • Parallel CLIs: stop and spawn run under a best-effort lockfile (~/.reins/daemon.lock, new lock.ts), and the decision is re-made once the lock is held.
    • A command can no longer /shutdown a daemon another command just started, and parallel spawns collapse into one.
    • When a current daemon is already running, commands never touch the lock.
    • findDaemon prefers the lowest live port, which is the one lowerPortRival keeps.
  • reins doctor: flags a version mismatch.
    • Older daemon: run reins restart.
    • Newer daemon: upgrade this CLI or check which -a reins, since a restart from the older CLI would downgrade the daemon.
  • reins status: shows a hint when the daemon is older than the CLI.
  • Docs: reins allow, docs/RUNNING.md, the web command table, and a new FAQ entry ("How do I update reins?") are updated. Changeset: @karnstack/reins minor.

The old restart (launchd/systemd service restart) was removed in 4e7cf33. This one is for the spawn-on-demand daemon, so the helpText test no longer asserts that restart is absent.

Testing

  • pnpm lint, pnpm typecheck, and the CLI tests pass (246). Tests cover the lock (real temp dirs, stale by dead pid and by age, timeout), ordering under the lock, a rejected /shutdown, a failed auto-restart, the 0.0.0 guard, each restart outcome, and the doctor check in both directions.
  • Checked against a logged-in Chrome, using daemons from copies of the build stamped v0.4.0 and v0.6.0:
    • reins restart with a browser connected: back in about 1.3s. With nothing running, it starts a daemon.
    • 5 parallel reins tabs against a v0.4.0 daemon: all exit 0, one restart notice, one daemon left (v0.5.0), no lockfile left over.
    • 5 parallel reins tabs with no daemon running: all exit 0, one daemon.
    • v0.6.0 daemon with the v0.5.0 CLI: doctor says the CLI is outdated, and reins tabs runs without downgrading the daemon.

🤖 Generated with Claude Code

karngyan and others added 2 commits September 27, 2026 01:54
After `npm i -g @karnstack/reins@latest` the background daemon kept
running the old code until someone knew to `reins kill` it. Now:

- ensureDaemon restarts a live daemon whose /health version is older than
  the CLI. Only older: a newer daemon is left alone so two installed CLI
  versions can't bounce it back and forth.
- `reins restart` stops the daemon, waits for its port to go quiet, spawns
  a fresh one and waits for previously connected browsers to reconnect.
- `reins kill` now waits until the daemon has actually exited, so a
  command straight after it can't reach the dying process.

The old service-manager `restart` (launchd/systemd) was removed in
4e7cf33; this one is for the spawn-on-demand daemon, so the help-text
guard against it is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups for the restart / restart-on-upgrade work:

- stopDaemon treats a rejected /shutdown as "already going"; the /health
  poll decides.
- Stop and spawn run under a best-effort lockfile (~/.reins/daemon.lock,
  stale by dead pid or age, never blocks longer than 10s), and the
  decision is re-made once it's held. A parallel CLI can no longer
  /shutdown the daemon another one just started, and parallel spawns
  collapse into one. A daemon found under the lock counts as fresh, so
  callers wait for the extension to reconnect.
- Auto-restart that can't stop the old daemon warns and uses it as-is
  instead of failing every command; explicit restart/kill still error.
- "0.0.0" (unreadable package.json) never triggers a restart.
- findDaemon prefers the lowest live port, the one lowerPortRival keeps.
- reins restart reports each case truthfully and exits 1 when browsers
  that were connected don't come back within 15s.
- reins doctor flags a daemon/CLI version mismatch; reins status hints
  when the daemon is older. Docs and changeset match what auto-restarts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@karngyan

Copy link
Copy Markdown
Contributor Author

Follow-ups from the review, in d98bb8c:

  • Parallel CLIs: stop and spawn run under a best-effort lockfile (~/.reins/daemon.lock), and the decision is re-made once the lock is held. Another CLI can no longer /shutdown a daemon that was just started, and parallel spawns collapse into one. The common case (a current daemon is already running) never touches the lock.
  • stopDaemon treats a rejected /shutdown as already stopping. A failed auto-restart warns and keeps using the old daemon instead of failing every command. A 0.0.0 version never triggers a restart. findDaemon prefers the lowest live port.
  • reins restart reports each outcome accurately and exits 1 when browsers that were connected don't come back within 15s. reins doctor flags a version mismatch, and reins status hints when the daemon is older.

Checked against a real Chrome, with a daemon from a copy of the build stamped v0.4.0:

  • 5 parallel reins tabs against the v0.4.0 daemon: all exit 0, one restart notice, one daemon left (v0.5.0), no lockfile left over.
  • 5 parallel reins tabs with no daemon running: all exit 0, one daemon.

Before the "found under the lock counts as fresh" fix, 4 of those 5 failed with no browser connected.

244 tests pass; lint and typecheck are clean.

doctor told users to `reins restart` on any daemon/CLI version mismatch.
When the daemon is newer (a second, newer install started it), a restart
from this older CLI downgrades the daemon, and the newer CLI's next
command bumps it straight back up. For that direction doctor now says to
upgrade this CLI or look for a second install with `which -a reins`.
Versions that differ only by pre-release tag pass, matching the restart
logic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@karngyan
karngyan merged commit 7736109 into main Sep 26, 2026
2 checks passed
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.

1 participant