Repository navigation
deploy: a check that only checks, every 4h, and a header that says what it found (#438) - #1364
Merged
Merged
Conversation
…at it found The dashboard said "deployment current" when the running build matched this box's own install record, which says nothing about hestia main (#438), and "check for updates" ran a full deploy cycle. Nothing checked on a schedule. - hestia-deploy records each sync's verdict in deploy-check.tsv (up-to-date / available / checking / failed, with the time); --check no longer self-updates and a hold no longer stops it - hestia-deploy-check.{service,timer}: --check every 4h (02/4:17), installed by install.sh and by the next full cycle on boxes that predate it - daemon: DeploymentHealth.check / check_capable and POST /api/operator/deployment/check, witnessed before it starts only the check unit - dashboard: "up to date · checked <time>", "update available", "checking…", "install incomplete"; the button checks, and becomes "update" when there is one Asked for by the operator on tl-dell, 2026-10-21. Review-gate blocks in the PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Reviewed current head: separates update check from deployment and surfaces checked/version state with scheduled check cadence. Three CI workflows green. No blocker found, contingent on check-only path truly not installing/restarting (as specified). |
dp-web4
added a commit
that referenced
this pull request
Oct 22, 2026
…here (Nova hold); merge main Merges origin/main (#1364, deploy check-only). The one conflict was the DEPLOYED log line: it keeps post_deploy=$post_deploy and #1364's write_check_result after it. Nova's hold: the post-deploy directory extends the deploy's privileged execution surface. Every check is now on the RESOLVED path: - $HESTIA_HOME and post-deploy.d must be owned by the deploy user and not group/world writable, and post-deploy.d must not be a symlink; otherwise nothing runs: refused(post-deploy.d not operator-only). - a step's realpath must be a regular file directly in post-deploy.d, owned by the deploy user and not group/world writable. A symlink out of the directory is skipped ("escapes post-deploy.d"), always: no allowlist. A link into a repo a governed member can write (SAGE) would let that member choose what the deploy runs, so the operator installs a copy. - the checked path is the one executed, with stdin from /dev/null and output to the log file (no pipe a lingering grandchild can hold), bounded by timeout -k (KILL after POST_DEPLOY_KILL_SECS for a step ignoring TERM) where timeout(1) has -k. A timeout is logged as one. A failure is caught with `|| rc=$?`, so the caller's next line runs under set -euo pipefail. post_deploy_step_test.py now extracts the two helpers with the function and covers: escape skipped and not run; a link inside the dir runs; a group-writable target, directly and via an inside link, skipped; a group-writable dir, group-writable home, and symlinked dir each refuse all; a failing step and a TERM-ignoring hung step that leaves a grandchild holding its output each return within the bound, the next step runs, and a sentinel after the call prints. Sabotaging each check (escape, target perms, dir/home refusal, symlinked dir, home, kill-after, failure containment, build export) fails the test. README: the CBP install is `install -m 0755` of the snapshot script, which finds the workspace from HESTIA_WORKSPACE (set in the deploy unit for the inventory step). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014LYVQN8U1xqy6U6pbyUDvH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The operator on tl-dell (2026-10-21) found that the dashboard's two deployment words mean something other than what they say:
They were right on both counts:
hestia-deploy --checkwasn't check-only, because it self-updated~/.local/bin/hestia-deploy.What the operator asked for:
What
Deploy script (
deploy/from-main/hestia-deploy.sh)$HESTIA_HOME/deploy-check.tsv, as<state> <checked_at> <running> <latest>. The states arechecking,up-to-date,availableandfailed, and the file is written atomically.up-to-dateafterDEPLOYED.--checkno longer self-updates the script, and a hold no longer skips it. A hold protects builds and restarts, and a check does neither.failed.New units
hestia-deploy-check.service:hestia-deploy --check, withSuccessExitStatus=3because "update available" is a result, not a failure.hestia-deploy-check.timer:02/4:17, two hours off the deploy timer's marks so the two never contend for the lock.deploy/fleet/install.shinstalls both and enables the check timer on first install. The deploy timer stays off by default, as before.Daemon
DeploymentHealth.checkholds{state, at, latest}, read from the file against the running build:availablefor a build that has since been installed readsup-to-date, compared by commit (Deploy must update itself: frictions the upgrade-path harness found (fresh-host bootstrap, atomic set, tag upgrade, unstable build ids, seeds, units) #1250), not by abbreviation;up-to-datefor a build that is no longer the one running readsunchecked;failed.DeploymentHealth.check_capableis true when the check unit is installed. It's read from disk, so the dashboard poll still spawns no process (A visible dashboard tab stalls every request ~1.2 s every 20 s: /api/agents runs the inventory with a blocking Command::output() inside an async handler, and the 300 s gate-watch pass runs it under the state lock #1183).POST /api/operator/deployment/check:deployment_check_requested) before the trigger, and the outcome record says whether it was witnessed;requestedso the UI shows "checking…" immediately;hestia-deploy-check.service.Dashboard header
● up to date · checked <time>! update available · checked <time>● checking for updates…! update check failed · <time>● not checked yet/● not checked since this build! install incomplete(the old "stale")● update status unknown(no check unit: macOS, or a box before its next update)The tooltip lists the running build, main's build at the last check, the check schedule and the last deploy run. The words "deployment current" are gone.
Rolling out to an existing box
The first update after this merges installs the new script but runs the old one, which has already been read into memory, so it doesn't install the check units. The new daemon then shows
● update status unknownwith an update button. One more update installs and enables the check timer. After that, the header is driven by the check. Alternatively, re-rundeploy/fleet/install.sh.Tests
deploy/from-main/check_mode_test.pyruns the real script in--checkagainst a throwaway HOME, local bare origins and a stub daemon, with no network and no real unit.failed.--checkself-updated, recorded nothing, and was stopped by a hold.deploy/fleet/deploy_supervisor_step_test.pynow also checks that the check units are installed, that the check timer is enabled on first install and left alone on a re-run, and that the deploy timer still isn't enabled.deploy_check_reports_the_supervisors_verdict_against_the_running_builddeployment_check_starts_the_check_unit_and_never_the_deploydeployment_check_in_flight_only_while_requested_or_checkingtools/dashboard_deployment_update_contract_test.py(now with 7 check assertions),operator_surfaces_spec_test.py,operator_surfaces_ratchet_test.py,no_path_literals_test.py(the new units registered, the check service at 1 literal),dashboard_no_undef.mjsand the other dashboard contract tests.cargo test --lib(pinned web4 31990db7): 1256 passed, 0 failed, 2 ignored. The three new tests were also run by name and pass.Review gate
Addresses #438: the header now answers "is the daemon current with main?" from a dated check. It doesn't add #438's second axis (lag size and whether it touches
core/src). Follows #1334.🤖 Generated with Claude Code