fix(banner): include local HEAD in the update-check cache key - #72013
fix(banner): include local HEAD in the update-check cache key#72013Kyzcreig wants to merge 1 commit into
Conversation
An in-place `git pull` / rebase moves HEAD without changing VERSION or
HERMES_REVISION -- and for a source install tracking a fork BOTH are None, so
the existing cache key {rev, ver} is constant across the update. A stale
"commits behind" count therefore survived the full 6-hour TTL: `hermes
--version` kept printing e.g. "182 commits behind" immediately after a
successful pull that brought the checkout up to date.
Add the active checkout's HEAD SHA to the cache key so an in-place update
self-invalidates the cached count. The read is computed AFTER the docker
short-circuit, so containers (which ship without .git) never shell out to git
here, and it reuses the existing _resolve_repo_dir() + _git_stdout() helpers
rather than adding a second subprocess wrapper. `hermes update`'s explicit
_invalidate_update_cache() still works; this covers manual git updates too.
Test changes: two existing tests observed the old call sequence.
test_check_for_updates_uses_cache now asserts the stronger property (only the
cheap HEAD read fires; a fetch/rev-list raises) instead of "no git at all", and
test_check_for_updates_expired_cache's count moves 4 -> 5 for the added HEAD
read. New test test_check_for_updates_invalidates_when_head_moved pins the
actual bug: fresh timestamp + same version + different HEAD must recompute.
Verification:
- New test + the expired-cache count test fail on unpatched main (RED),
pass with the fix.
- tests/hermes_cli/test_banner.py, test_banner_git_state.py,
test_update_check.py, test_update_stale_dashboard.py,
test_cmd_update_docker.py: 68 passed, 0 failed.
Duplicate of #20653: both make the update-check cache depend on the local Git HEAD so manual source updates invalidate a stale commits-behind banner. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused cache-correctness fix. The premise remains valid on current main: hermes_cli/banner.py:311-316 returns a fresh cache before the local checkout is resolved at hermes_cli/banner.py:320-335.
Problems
- The added
_local_head_sha()docstring says bothVERSIONandHERMES_REVISIONareNonefor source installs, butVERSIONis defined athermes_cli/banner.py:68. The code is correct; this is documentation-only. The new regression-test docstring repeats the same statement.
Suggested changes
- Say that
VERSIONremains unchanged across an in-place pull and thatHERMES_REVISIONis normally unset for source installs. - GitHub currently reports this branch as conflicted. Main commit
6b81590c55bcc9c1001a33b51b528784f96c6a07pruned the surrounding update-cache tests, so salvage should retain the new HEAD-mismatch regression in the current test file rather than restore the removed tests.
Automated hermes-sweeper review.
| def _local_head_sha() -> Optional[str]: | ||
| """Return the active checkout's HEAD SHA, or None for non-git installs. | ||
|
|
||
| Used as part of the update-check cache key so an in-place ``git pull`` / |
There was a problem hiding this comment.
VERSION is not None for source installs (hermes_cli/banner.py:68 defines it as a release string); it simply does not change after a manual pull. Please reword this to distinguish unchanged VERSION from normally-unset HERMES_REVISION.
|
Superseded by #99294, which carries this change rebased onto current |
Problem
check_for_updates()caches the "commits behind" count for 6 hours, keyed on{rev, ver}:Neither component changes when a user updates in place:
HERMES_REVISIONis nix-only, soembedded_revisNonefor source installs.VERSIONonly moves on a release bump, not on agit pull.So an in-place
git pull/ rebase movesHEADwhile the cache key stays constant. The stale count then survives the full TTL, andhermes --versionkeeps printing e.g. "182 commits behind" immediately after a successful update that brought the checkout up to date.hermes updatecalls_invalidate_update_cache()explicitly and is unaffected; this is the manual-git path.Fix
Add the active checkout's
HEADSHA to the cache key, so an in-place update self-invalidates.Two implementation details that matter:
.git(see.dockerignore) and already returnNoneearlier in the function; placing the read below that guard means containers never shell out to git for this._resolve_repo_dir()(banner.py:333) and_git_stdout()(banner.py:157) already exist onmainand already handle the not-a-git-install and the Windows-encoding cases, so_local_head_sha()is four lines rather than a secondsubprocess.runwrapper with its own timeout/encoding policy.No new config key, no new env var.
Test changes
Two existing tests asserted the old call sequence and had to move with it. Both were tightened rather than loosened:
test_check_for_updates_uses_cachepreviously assertedmock_run.assert_not_called()— "no git at all". Agit rev-parse HEADnow legitimately runs on the fast path. Rather than relax the assertion to a call count, the test now uses aside_effectthat returns the HEAD and raisesAssertionErroron any other git invocation. That is a strictly stronger claim than the original: it proves nofetchand norev-listhappen, and it names which call is allowed.test_check_for_updates_expired_cachecounted 4 subprocess calls (origin probe + shallow probe + fetch + rev-list); it is now 5 with the cache-key HEAD read. The comment enumerating them was updated to match.New:
test_check_for_updates_invalidates_when_head_movedpins the actual bug — fresh timestamp, sameVERSION, differentHEADmust recompute and must rewrite the cache with the new head. It fails on unpatchedmain.Verification
Neighbor suites on this branch:
test_cmd_update_docker.pyis included deliberately, since the placement of the HEAD read relative to the docker short-circuit is the one behavioral risk in the diff.Supersedes
This replaces #40157 (same fix, same premise). That branch is 3,406 commits behind and hard-conflicts on apply. The premise was re-verified on current
mainbefore writing this: the cache key atbanner.py:298-299is still{rev, ver}only, and the write at:324still omitshead, so the bug is unfixed upstream.The re-port is also smaller than the original: #40157 had to introduce its own
subprocess.runblock because_resolve_repo_dir()/_git_stdout()did not exist yet at the time. They do now, so this version delegates to them.The review on #40157 noted one gap — no test for the actual HEAD-mismatch transition.
test_check_for_updates_invalidates_when_head_movedis that test.Happy to close #40157 in favor of this once maintainers confirm the approach.