* feat: add service-managed self-update * chore: add temporary self-update test scripts * chore: simplify temporary script markdown fences * chore: run frontend in temporary self-update scripts * fix: show self-update job progress after apply * chore: add real npm self-update test script * fix: isolate npm self-update prefix * fix: detect npm service installs from bin shim * chore: reset service before real npm update test * chore: expand real npm status diagnostics * fix: preserve service home during self-update * fix(cli): tee self-update helper output to a log file * fix(cli): avoid launchd bootstrap race when reloading the macOS service * fix(backend): run macOS self-update helper once via transient launchd plist * fix(web): show self-update progress and lock the apply button until the update lands * fix(cli): reinstall via the kandev wrapper on Homebrew self-update * chore: remove temp self-update test scripts * fix(web): drop unused apply path from useUpdates hook * fix(backend): snapshot install state once in apply preflight * docs: align self-update docs with transient LaunchAgent flow * chore(release): run cli bundle from apps workspace root * fix(backend): guard against concurrent self-update applies * fix(backend): check scheme and helper paths in self-update apply * fix(backend): drop duplicate binary name from npx manual command * fix(cli): allow --help for service self-update without --intent * fix(cli): harden self-update dir perms and darwin uid resolution * fix(web): set required POST fields last in applyUpdate * docs: use JSON body notation for the apply endpoint * fix(backend): expire self-update guard so a stuck helper cannot wedge apply * refactor(cli): collapse redundant writeSync branch in self-update log
2.3 KiB
0012: Service-only UI self-update
Status: accepted Date: 2026-05-29 Area: backend, frontend, cli
Context
Kandev can be installed through npm/npx or Homebrew and can also run interactively from a shell. Updating a running interactive process from the UI is brittle: the process may not have a stable service manager, restart semantics, or a durable place to hand work off before the backend exits.
The service installer already owns the OS-specific restart contract for systemd and launchd. That makes it the right boundary for UI-driven self-update.
Decision
UI self-update is supported only when the backend proves all of these are true:
- It is running under
kandev service install. - The unit/plist contains kandev's managed marker and
KANDEV_SERVICE_*environment. <KANDEV_HOME_DIR>/service/install.jsonmatches the running service manager, mode, install kind, and service file.- The service is user-mode, not
--system. - The install kind is
homebrew,npm, ornpx.
The backend returns this state from GET /api/v1/system/updates. The frontend renders Apply update only when the backend reports apply_supported=true; otherwise it shows manual update commands.
POST /api/v1/system/updates/apply writes an intent file and starts a helper outside the running service's lifetime:
- Linux user services use
systemd-run --user --collect. - macOS user agents use a one-shot transient LaunchAgent plist (
RunAtLoad=true,KeepAlive=false) bootstrapped vialaunchctl bootstrap. (launchctl submitwas rejected: its implicit KeepAlive re-ran the one-shot helper in a loop.) - E2E/dev tests can fake the helper with
KANDEV_E2E_MOCK=true.
The CLI owns the helper planner through a hidden kandev service self-update --intent <path> command. It upgrades the package via Homebrew, npm, or npx, re-runs kandev service install, then restarts the service.
Consequences
- The UI never offers a destructive update button for an unknown install.
- System services remain explicit terminal operations because they need privilege and differ by host policy.
- Tests can cover the backend, CLI planner, and UI gate without mutating a developer machine.
- The service installer must keep metadata and
KANDEV_SERVICE_*env vars current whenever it rewrites a unit/plist.