Files
hermes-agent/hermes_cli/update_cmd_fleet_checkout.py
Teknium ea665a911d fix(update): one custody policy for every updater git and Node child
R2: hermes_cli/update_custody.run_git is THE updater git runner;
update_cmd._git_run, update_cmd_git._git_run, update_cmd_stash,
update_cmd_check._git, gitlock, update_cmd_zip and the fleet ancestry probe
all go through it.

- every updater git call carries -c gc.autoDetach=false
  -c maintenance.auto=false: no detached gc/maintenance child ever forks
  under (POSIX) or outlives (Windows) the checkout lock;
- POSIX: only local mutators (reset, checkout, merge, stash, rebase,
  update-ref, clean, apply, commit, ...) inherit the lock fd, with
  core.fsmonitor=false; fetch/ls-remote/credential and readers run without
  it, so a credential-cache daemon never holds the checkout (reviewer: up to
  2 h of installer waits). On Linux those fd-less children die with the
  updater (PR_SET_PDEATHSIG) instead of rewriting refs under the next owner;
- Windows: inside an update every child is created suspended, assigned to
  the owner's kill-on-close job and only then resumed; the Node build
  (pm.progress.run_contained) joins it through a stdlib launcher.

The fork sync fast-forwards locally (merge --ff-only
refs/remotes/upstream/main after its fetch) instead of a networked pull
under the mutator fd.
2026-10-06 04:36:02 -07:00

50 lines
2.3 KiB
Python

"""Checkout ancestry for the update-restart obligation (``update_cmd_fleet`` sibling).
The obligation records the SHA a pull landed on; the checkout may later sit past it by a carried
local commit (a cherry-picked hotfix). Whether that recorded SHA is still *contained* in HEAD is
the question these readers ask, so the live fleet can be held to the code on disk (#119367).
"""
from __future__ import annotations
import logging
import subprocess
from hermes_cli._subprocess_compat import NO_LAZY_FETCH_ENV, noninteractive_git_env
logger = logging.getLogger(__name__)
def checkout_contains(sha: str) -> bool:
"""True when ``sha`` is an ancestor of (or equal to) the code this checkout runs; False on any
probe failure.
Fail-closed on purpose: an unknown ancestry is not evidence that the fleet serves the update.
A Docker/Cloud image has no ``.git``; its identity is the baked build stamp
(``build_info.get_code_identity`` → ``source == "build-file"``). There is no history to walk, so
"contained" collapses to "equal to the stamp" — without this the probe was always False on an
image and the pending-restart catch-up printed "every gateway serves the checkout" and "still
off the checkout code" in the same breath.
"""
from hermes_cli.build_info import get_code_identity
from hermes_cli.update_cmd import _m
identity = get_code_identity() or {}
if identity.get("source") == "build-file":
stamped = str(identity.get("sha") or "")
return bool(stamped) and (stamped == sha or stamped.startswith(sha) or sha.startswith(stamped))
try:
from hermes_cli.update_custody import run_git
result = run_git(
["git"], ["merge-base", "--is-ancestor", sha, "HEAD"],
cwd=_m().PROJECT_ROOT, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=10,
# A SHA this checkout lacks is not contained; asking the promisor remote for it would
# download that commit's entire history (on-demand fetches send no "have" lines).
env={**noninteractive_git_env(), **NO_LAZY_FETCH_ENV},
)
return result.returncode == 0
except Exception as exc:
logger.debug("Checkout ancestry probe for %s failed: %s", sha[:10], exc)
return False