Files
hermes-agent/hermes_cli/update_cmd_fleet_checkout.py
teknium1 30056d612b fix(update): a carried local commit no longer strands the fleet-restart obligation
`_marker_only_restart_obsolete` bailed out with "a newer pull moved HEAD" whenever
`checkout_sha != expected_sha`, and otherwise held the fleet to `expected_sha` exactly.
A cherry-picked hotfix on top of the pulled SHA moves HEAD without any pull arming a
fresh obligation, so the recorded one could never discharge: every CLI start warned
and every no-op `hermes update` exited 1 through the armed veto in
`_pending_fleet_restart_needed`, while the gateway verifiably served HEAD.

The bail-out now fires only when HEAD no longer CONTAINS `expected_sha`
(`checkout_contains`, `git merge-base --is-ancestor`, fail-closed on any probe
failure), and the live fleet is held to the checkout SHA — the code it actually runs.
A gateway on genuinely stale code still keeps the obligation armed.

Closes #119367
2026-09-23 07:01:11 -07:00

31 lines
1.1 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
logger = logging.getLogger(__name__)
def checkout_contains(sha: str) -> bool:
"""True when ``sha`` is an ancestor of (or equal to) the checkout HEAD; False on any probe failure.
Fail-closed on purpose: an unknown ancestry is not evidence that the fleet serves the update.
"""
from hermes_cli.update_cmd import _m
try:
result = subprocess.run(
["git", "merge-base", "--is-ancestor", sha, "HEAD"],
cwd=_m().PROJECT_ROOT, capture_output=True, text=True, timeout=10,
)
return result.returncode == 0
except Exception as exc:
logger.debug("Checkout ancestry probe for %s failed: %s", sha[:10], exc)
return False