Skip to content

fix(banner): bind update-check cache to local HEAD so banner refreshes after hermes update (#20728) - #20792

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/banner-stale-update-cache-20728
Closed

briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/banner-stale-update-cache-20728

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

Summary

  • Long-running TUI/gateway processes kept showing the pre-update ▲N commits behind banner after hermes update because the in-memory _update_result outlived both the on-disk cache wipe and a sibling-process pull.
  • The 6-hour disk cache's cached.get(\"rev\") == embedded_rev validity check was a no-op for git installs (both sides None), so even when read it would return a stale count for up to 6 h after git pull.
  • Bind the cache to the local HEAD short SHA at write time and require it to match at read time; kick a non-blocking daemon refresh when HEAD has moved since the in-memory snapshot was taken.

The bug

Two interacting issues — both reported with high specificity in #20728:

Bug 1 — in-memory freeze. tui_gateway/server.py:110 calls prefetch_update_check() once at startup. From then on, every get_update_result() call (including the dashboard info handler at tui_gateway/server.py:1424) returns the module-level _update_result set by the prefetch. There's no IPC, file-watch, or HEAD re-validation. After hermes update runs in a sibling CLI process and invalidates the on-disk cache via _invalidate_update_cache(), the running TUI process never re-reads it.

Bug 2 — cache validity is a no-op for git installs. check_for_updates() cached {ts, behind, rev: None} for git installs. The validity check cached.get(\"rev\") == embedded_rev only meant something for nix builds (HERMES_REVISION set). On git installs both sides were None, so the 6-hour TTL was the only invalidator — after a manual git pull the stale behind count survived for hours.

The fix

hermes_cli/banner.py:

  1. check_for_updates() now writes local_head (HEAD short SHA) into the cache JSON for git installs, and rejects cached entries whose local_head doesn't match the current HEAD. Legacy caches without the field also fall through to a refresh so the HEAD-binding contract takes effect on the very next read. The cheap git rev-parse --short=8 HEAD is only invoked when the timestamp + rev pre-checks already pass, so the no-fetch happy path stays no-fetch.
  2. New helper _current_local_head_short() — single source of truth for the cache-key HEAD lookup. Never does a fetch.
  3. prefetch_update_check() records the HEAD it observed alongside _update_result.
  4. get_update_result() compares the current HEAD against the snapshot HEAD; on mismatch it kicks a non-blocking daemon thread (guarded by a non-blocking Lock so concurrent renders don't pile up refreshes) that recomputes _update_result. The caller still returns immediately with the current snapshot — refresh is asynchronous on purpose so banner renders never block on a git fetch.

Test plan

  • Focused regression tests added in tests/hermes_cli/test_update_check.py:
    • test_check_for_updates_invalidates_when_head_moved — covers Bug 2 (cache rejected when local_head doesn't match)
    • test_check_for_updates_legacy_cache_invalidated — covers the migration of pre-existing caches without the field
    • test_check_for_updates_writes_local_head_to_cache — confirms the new field is persisted
    • test_get_update_result_refreshes_after_head_moves — covers Bug 1 (long-running process kicks a refresh on HEAD change)
    • test_get_update_result_no_refresh_when_head_unchanged — confirms we don't waste threads on the steady-state path
    • test_get_update_result_does_not_block_caller — guards against any future change that would make the refresh synchronous
  • Updated test_check_for_updates_uses_cache and test_check_for_updates_expired_cache for the new cache schema (added local_head to the cache write/read paths)
  • Adjacent suite: tests/hermes_cli/test_banner.py, test_banner_git_state.py, test_banner_skills.py — all 15 pass
  • Regression guard: without the fix, cached.get(\"rev\") == embedded_rev is None == None and the stale entry is returned indefinitely; the new tests fail on origin/main for the same reason and pass on this branch.

Related

…s after hermes update (NousResearch#20728)

The startup banner kept showing "▲N commits behind" after a successful
`hermes update` (and after a plain `git pull`). Two interacting bugs:

1. **In-memory snapshot frozen for long-running processes.** The TUI
   gateway calls `prefetch_update_check()` once at startup, which sets
   the module-level `_update_result`. Every later call to
   `get_update_result()` returns that same value — even when `hermes
   update` deleted the on-disk cache or a sibling process moved HEAD.
   `tui_gateway/server.py:1424` reads it on every dashboard request.

2. **Cache validity check was a no-op for git installs.** The
   `cached.get("rev") == embedded_rev` check only meant something for
   nix builds (where `HERMES_REVISION` is set). For git installs both
   sides were `None`, so the 6-hour TTL was the only invalidator. After
   a manual `git pull` the cached "behind" count survived for hours.

Bind the cache to the local HEAD short SHA. `check_for_updates()` writes
`local_head` alongside the count and rejects cached entries whose
`local_head` doesn't match the current HEAD; legacy caches without the
field also fall through to a refresh on first read. `get_update_result()`
remembers the HEAD that produced its in-memory snapshot and, when the
current HEAD differs, kicks a non-blocking daemon refresh so the next
render picks up the fresh value. Caller is never blocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 6, 2026 15:17
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard labels May 6, 2026
@alt-glitch

Copy link
Copy Markdown

Duplicate of #20653 (same approach: store repo_head in update-check cache, invalidate when HEAD changes). Also overlaps #18236, #9670, #5359, all addressing the same stale banner cache issue. #20792 additionally fixes the in-memory freeze in long-running TUI processes (Bug 1 from #20728), which is novel.

@briandevans

Copy link
Copy Markdown
Contributor Author

Thanks @alt-glitch — agreed. Bug 2 (HEAD-binding the on-disk update-check cache) is functionally the same approach as #20653: both add a HEAD field to the cache entry and invalidate when it moves. #20653 was opened first, so deferring to it on that piece makes sense.

The novel piece here is Bug 1: the module-level _update_result in long-running TUI/gateway processes outlives both the on-disk cache wipe (hermes update) and any sibling-process git pull, because there's no IPC, file-watch, or HEAD re-validation after the one-shot prefetch_update_check() at startup (tui_gateway/server.py:110tui_gateway/server.py:1424). That's not addressed by #20653, #18236, #9670, or #5359 — all of those fix the on-disk cache only.

Happy to take either path here:

  1. Narrow this PR to just Bug 1 once fix: preserve update cache correctness for source installs #20653 lands — drop the check_for_updates() cache change (defer to fix: preserve update cache correctness for source installs #20653), keep _maybe_refresh_in_background + _update_result_head + the get_update_result refresh-on-HEAD-moved logic.
  2. Close fix(banner): bind update-check cache to local HEAD so banner refreshes after hermes update (#20728) #20792 and re-open a fresh Bug-1-only PR after fix: preserve update cache correctness for source installs #20653 merges.

Whichever the maintainers prefer.

@briandevans

Copy link
Copy Markdown
Contributor Author

Closing — superseded by @alt-glitch's pointer to #20653, which is the better-positioned fix for the cache-HEAD binding here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: TUI banner shows stale "N commits behind" after successful hermes update

2 participants