Repository navigation
feat(cli): CTC-4646 — a self-hosted runner moves to the approved release on its own, between phases - #386
Conversation
d8a6ba8 to
d8fec23
Compare
…ase on its own, between phases `catalyst runner update` reads the release Catalyst approved for self-hosted machines (GET /api/v1/hosts/desired-release, read with the host's own credential from inside a one-off supervisor container, so the secret never leaves Docker). It moves only when the release parses strictly (three digests from the three Catalyst repositories, never a tag or latest, release id matching the digests, a generation newer than the last applied), no phase container exists for this host, all three images pull natively by digest, and, on a Mac with private thoughts storage, that storage re-attests on the new supervisor with the same authority. Between phases is enforced, not sampled: after the pulls it stops the supervisor so it admits nothing, looks again, and if a phase started in between it brings the supervisor back on its old images and waits. A release counts as applied only when the running containers are its images and the supervisor logged host_ready since it started, so a .env an interrupted run left behind is reconciled, never trusted. Every path that does not end on the new release attests Mac thoughts storage back onto the images the machine keeps. The lock is taken over only when its owner process is gone. Then it rewrites only the three image lines in .env (keeping the previous file as .env.release-previous), runs docker compose up, and waits up to five minutes for the new supervisor to log host_ready. If it does not, the previous file and images come back and the run exits 1. Every other answer keeps the current images. release-state.json records the applied generation and the last outcome. After a verified update it removes the images it replaced (a runner image is about 9 GB against the 25 GiB free-disk floor); a rollback or failure keeps them. `catalyst runner auto-update on|off|status` schedules the check every 15 minutes (a LaunchAgent on macOS, on calendar minutes 0/15/30/45 because a StartInterval job sat pended for hours on a busy Mac, a systemd user timer on Linux, which says when lingering is off). Setup turns it on after the runner starts; a failure there is reported and never fails setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGe7UXEGNZW1GenNuUZRDU Change-Id: I9624b6ebe5c2ca63f14cb48f9fff34e2deaec99f
|
Review custody for CTC-4646: this PR has already received its ONE local Codex full review. It found three P1 and two P2 issues. The source owner fixed all five before the current amended head
The narrow post-review fix-check recorded by selfhost-lead passed TypeScript, 319 tests, and four targeted mutants. Later image cleanup and calendar-schedule changes received narrow mutant checks, not a second full review. Physical self-update proof on mini-2 (launchd) and amd64 (systemd user timer) completed without a restart, with host-ready readoption; ADV-17 then ran phases on the approved runner generation. Current-head Node 22/24/26, Bun runtime, evals and CodeQL checks are green. The source owner retains the original reviewer session metadata and was asked to add it to the handoff; no second Codex review should be requested for this PR. |
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 no unresolved review threads before merge
|
Merge Queue Status
This pull request spent 6 minutes 44 seconds in the queue, including 5 minutes 51 seconds running CI. Required conditions to merge
|
0.16.17 carries #386 (a self-hosted machine follows Catalyst's approved release by itself, between phases) and #392 (setup starts new machines on the current approved images, CTC-5955). Patch on the unified 0.16 train: both changes are additive. Version stamped with version:sync; release-train check aligned. Claude-Session: https://claude.ai/code/session_01LGe7UXEGNZW1GenNuUZRDU Change-Id: I0284afb069442d7d48cb5f59c96b7177af5ea207 Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A self-hosted runner moves to the release Catalyst approved, on its own, between phases. It never pulls a tag, and it goes back to the previous images if the new supervisor does not come up.
Depends on coalesce-labs/catalyst-cloud#12379 (CTC-5926), which serves
GET /api/v1/hosts/desired-release. Until that is deployed, the read answers 404 and every run keeps the current images.What a run does
compose run --rm --no-deps), the same way setup reads enrollment, so the host secret never leaves Docker..env(previous file kept as.env.release-previous), runsdocker compose up, and waits up to 5 minutes forhost_readyfrom the new supervisor..envand images and exits 1. Every other answer keeps the current images.release-state.jsonkeeps the applied generation and the last outcome.catalyst runner auto-update on|off|statusschedules it every 15 minutes: a LaunchAgent on macOS, a systemd user timer on Linux (it says when lingering is off). Setup turns it on after the runner starts, and never fails because of it.Review
One local Codex review on d8a6ba8 found five issues, all fixed in this head:
.envleft by an interrupted run was trusted as applied. The run now reconciles against the running containers.host_readythat never came.Narrow fix-check in sbx:
.env, no custody restore, and a lock that ignores its owner.No re-review.
Proof
sbx and mini, at 1519f38: typecheck clean, 319 tests passed, build and prose check clean. Mutants caught: no re-check after stop, trusting
.env, no custody restore, a lock that ignores its owner, no image cleanup.Physical, 2026-10-17, test workspace. catalyst-cloud#12379 (CTC-5926) merged and deployed, and its publisher approved generation 1
hr-eb9a0e83273ec210-9a96426c93e32173-63a9b20c3be3dc3e. Then:supervisor_readoptedandhost_ready.release-state.jsonrecords generation 1 and the previous digests.--checkon both saysnot_newer.eb9a0e83(research ran on another host), and the ticket reached PR: docs(sample-service): [details removed] — add README Updates note for self-hosted runners catalyst-e2e/sample-service#15.Found and fixed during the physical run (both in this head):
StartIntervalandRunAtLoadsat as "pended nondemand spawn" for 5 hours on a busy Mac. Calendar minutes 0/15/30/45 fire on time, so the Mac schedule now uses them.Docs: catalyst-cloud#12427 (draft) updates the self-hosting page and lands after the CLI release that carries this.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LGe7UXEGNZW1GenNuUZRDU