fix(bootstrap): self-heal a systemd unit left on a retired plugin root
The residue an interrupted update leaves behind is invisible: the server
runs, every doctor row reads OK, and the unit quietly still names a cache
directory Claude Code deleted during the update. Nothing surfaces it until
a reboot, when the dashboard does not come back and nothing recorded why.
- fastPathOk now rejects a stale unit, so the next SessionStart repairs it
instead of short-circuiting. `unitIsStale` is pure fs on purpose: that
path runs on every session and may not spawn a process.
- Repairing the unit is not restarting the server. A current, healthy
server keeps running and the corrected unit takes effect at the next
start (`pendingSystemdRestart = systemdOwned && needsServer`), and the
detached-spawn fallback is guarded by needsServer so a stale unit alone
cannot start a second server onto a bound port.
- systemdOwned now also requires the unit to be ACTIVE. Installed-but-
stopped owns nothing: skipping the stop there and asking systemd to start
would bind an occupied port and crash-loop under Restart=always.
- /agenthub-doctor gains a systemd row naming the consequence ("will NOT
come back after a reboot") and the fix. WARN, never FAIL — the dashboard
in front of you works, and having no unit at all is a normal setup.
The fast-path test pins the regression against an otherwise fully healthy
install, live server included; without a live server it would have passed
against the unfixed code either way.
This commit is contained in:
+23
-1
@@ -210,11 +210,33 @@ the Settings panel do — still stops it.
|
||||
it runs from your own shell, outside the unit's cgroup, so it may block and
|
||||
start in one step.
|
||||
|
||||
**A unit pointing at a retired plugin root repairs itself on the next session.**
|
||||
This is the residue an interrupted update leaves behind, and it is invisible
|
||||
while the dashboard is up: the server runs fine, every doctor row reads OK, and
|
||||
then a reboot starts a cache directory Claude Code deleted during the update —
|
||||
so the dashboard never comes back, for a reason nothing recorded. The fast path
|
||||
therefore checks it (`unitIsStale`, pure fs — it may not spawn a process on
|
||||
every `SessionStart`) and refuses to short-circuit, and the bootstrap rewrites
|
||||
the unit.
|
||||
|
||||
Repairing the *unit* is not restarting the *server*: when the running server is
|
||||
current and healthy, the corrected unit is simply left to take effect at the
|
||||
next start (`pendingSystemdRestart = systemdOwned && needsServer`). Restarting
|
||||
a working dashboard to fix a file that only matters at boot would be churn the
|
||||
user feels for nothing. For the same reason the detached-spawn fallback is
|
||||
guarded by `needsServer` — entering that branch for a stale unit alone would
|
||||
start a second server onto a port the live one already holds.
|
||||
|
||||
`systemdOwned` requires the unit to be **active**, not merely present: an
|
||||
installed-but-stopped unit owns nothing, and treating it as the owner would
|
||||
skip the stop and then ask systemd to bind an occupied port — a crash loop,
|
||||
courtesy of `Restart=always`.
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Does |
|
||||
|---|---|
|
||||
| `/agenthub-doctor` | Node version, bootstrap state, runtime deps, server liveness, duplicate hooks, CLI launcher + PATH, **tmux** (`WARN` when absent — only the run console, `agenthub lanes shell` and session reloads need it), **skill plugins** (see below), UI bundle |
|
||||
| `/agenthub-doctor` | Node version, bootstrap state, runtime deps, server liveness, duplicate hooks, CLI launcher + PATH, **systemd** (`WARN` when the unit names a retired plugin root — the dashboard will not survive a reboot — or when it is installed but stopped; no unit at all is fine), **tmux** (`WARN` when absent — only the run console, `agenthub lanes shell` and session reloads need it), **skill plugins** (see below), UI bundle |
|
||||
| `/agenthub-update` | Pull the latest `agenthub` release via `claude plugin marketplace update` + `claude plugin update agenthub@<marketplace> -y`, then reinstall dependencies and restart the server (`plugin-bootstrap.js --force`). The plugin pull needs a manual `/reload-plugins` afterward to take effect in the current session — not scriptable from a command. |
|
||||
| `/agenthub-open` | Rebuild the UI bundle and print the dashboard URL. Re-runs the bootstrap first when the server is down or still executing a plugin root that a mid-session `/plugin` update retired, so a fresh bundle never talks to an older server's routes |
|
||||
| `/ship-full <requirement>` | Put the lane on the 16-node `ship-feature` template, then run `/agenthub:ship-feature-lane` **in this session**. The in-session twin of `agenthub ship-full` (which spawns its own `claude` from a terminal) |
|
||||
|
||||
Reference in New Issue
Block a user