fix(banner): count "commits behind" against myfork/main on fork-tracking checkouts - #49
Conversation
…ot upstream, on fork-tracking checkouts On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call).
📝 WalkthroughWalkthroughThe banner update-check logic in ChangesFork-tracking update check baseline
Sequence Diagram(s)sequenceDiagram
participant Banner
participant check_for_updates
participant _check_via_local_git
participant fork_tracking
participant git
Banner->>check_for_updates: check_for_updates()
check_for_updates->>check_for_updates: read .update_check cache
alt cache hit (fresh)
check_for_updates->>check_for_updates: _set_update_baseline(cached_baseline)
check_for_updates-->>Banner: cached behind count
else cache miss / expired
check_for_updates->>_check_via_local_git: _check_via_local_git(repo_dir)
_check_via_local_git->>fork_tracking: detect_fork_tracking()
alt fork tracking active
_check_via_local_git->>git: git fetch myfork --quiet
_check_via_local_git->>git: git rev-list --count HEAD..myfork/main
_check_via_local_git-->>check_for_updates: (behind, BASELINE_FORK)
else upstream
_check_via_local_git->>git: git fetch origin --quiet
_check_via_local_git->>git: git rev-list --count HEAD..origin/main
_check_via_local_git-->>check_for_updates: (behind, BASELINE_UPSTREAM)
end
check_for_updates->>check_for_updates: write behind + baseline to .update_check
check_for_updates->>check_for_updates: _set_update_baseline(baseline)
check_for_updates-->>Banner: behind count
end
Banner->>check_for_updates: get_update_baseline()
check_for_updates-->>Banner: baseline string
alt baseline == BASELINE_FORK
Banner->>Banner: render "sync to myfork/main" remedy
else baseline == BASELINE_UPSTREAM
Banner->>Banner: render "run recommended update command" remedy
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
run_agent.py:2920: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 5756 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
hermes_cli/banner.py (1)
357-377:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winCache does not invalidate when baseline mode changes.
The comment on lines 362-364 states the baseline guard "prevents a fork-tracking checkout from reading an upstream-baseline cache (or vice versa)", but the cache validity check on lines 369-373 does not compare the cached baseline against the current detection result.
If a checkout transitions between fork-tracking and upstream-tracking (e.g., user adds/removes the
myforkremote), the stalebehindcount from the old baseline will be returned for up to 6 hours. The remedy text will be correct (line 374 sets the baseline from cache), but the commit count will be measured against the wrong ref.Consider adding a baseline check to the cache guard, or note in the comment that this edge case is intentionally tolerated.
Proposed fix to add baseline validation
+ # Pre-compute current baseline to validate cache + repo_dir = Path(__file__).parent.parent.resolve() + if not (repo_dir / ".git").exists(): + repo_dir = get_hermes_home() / "hermes-agent" + current_baseline = BASELINE_FORK if (repo_dir / ".git").exists() and _resolve_fork_tracking(repo_dir) else BASELINE_UPSTREAM + now = time.time() try: if cache_file.exists(): cached = json.loads(cache_file.read_text()) if ( now - cached.get("ts", 0) < _UPDATE_CHECK_CACHE_SECONDS and cached.get("rev") == embedded_rev and cached.get("ver") == VERSION + and cached.get("baseline", BASELINE_UPSTREAM) == current_baseline ): _set_update_baseline(cached.get("baseline", BASELINE_UPSTREAM)) return cached.get("behind")Alternatively, if this edge case (switching baseline mode mid-cache-lifetime) is rare enough to tolerate, update the comment to reflect the actual behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hermes_cli/banner.py` around lines 357 - 377, The cache validity check in the section starting at line 369 validates the timestamp, rev, and version but does not compare the cached baseline against the current baseline, even though the comment states the baseline guard prevents cross-pollution between fork-tracking and upstream-tracking modes. Add a baseline check to the cache validity condition to ensure that if the baseline mode has changed (e.g., user adds/removes the myfork remote), the stale cached behind count is invalidated. Determine the current baseline before the cache check and add a comparison of cached.get("baseline") against the detected current baseline to the conditional on lines 370-373.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@hermes_cli/banner.py`:
- Around line 357-377: The cache validity check in the section starting at line
369 validates the timestamp, rev, and version but does not compare the cached
baseline against the current baseline, even though the comment states the
baseline guard prevents cross-pollution between fork-tracking and
upstream-tracking modes. Add a baseline check to the cache validity condition to
ensure that if the baseline mode has changed (e.g., user adds/removes the myfork
remote), the stale cached behind count is invalidated. Determine the current
baseline before the cache check and add a comparison of cached.get("baseline")
against the detected current baseline to the conditional on lines 370-373.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9b23edb0-7784-457d-a3a2-4daef1452dae
📒 Files selected for processing (2)
hermes_cli/banner.pytests/hermes_cli/test_update_check.py
|
auto-review: approved, awaiting human merge + kanban_approve. Matrix checks (U1–U6, C1–C6)
Code-quality judgment (role-reviewer)Verdict: APPROVED (no Blocker/Major). API consumption matches
|
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
…ot upstream, on fork-tracking checkouts (#49) On an inverted-topology checkout (origin = NousResearch upstream, myfork = the operator fork the machine deploys from), the startup banner counted HEAD..origin/main and perpetually nagged "N commits behind" even on a freshly-synced tree. The remedy hint was also wrong: the upstream CLI auto-pull path resets to upstream and discards fork-only commits. banner.py predated the fork-tracking work (PR #47) and never consumed hermes_cli.fork_tracking. This wires _check_via_local_git to it: - Resolve fork-tracking config first (read-only consumer of detect_fork_tracking). When it applies, count HEAD..{cfg.fork_ref} (myfork/main), fetching the fork remote — never origin. The banner then reads "N behind your fork" semantics: 0 on a synced checkout, non-zero only on genuine local drift behind the fork. - Else (canonical upstream-tracking install) keep today's exact behavior: count behind origin/main, suggest the upstream CLI auto-pull command. That path stays byte-identical for PyPI/container/official-remote installs. - Fork-tracking case gets fork-appropriate remedy copy instead of the upstream update hint. - _check_via_local_git now returns (behind, baseline); check_for_updates threads the baseline into the 6h .update_check cache (new "baseline" key) and a module-level getter so a fork cache is never read as an upstream cache or vice-versa, and the render picks the correct remedy. Verified on the live inverted checkout: HEAD..origin/main=5 (old, wrong), HEAD..myfork/main=0 (new, correct) -> banner shows 0 behind / no nag. Adds regression tests for the fork baseline (counts against myfork, fetches the fork, reports drift), the unchanged non-fork path, and baseline cache isolation. Updates the one expired-cache test to assert the upstream-path git calls are present rather than an exact subprocess call count (the fork-detection probe adds one leading call). Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com> Co-authored-by: Sahil Marwaha <97122673+sahilm-ti@users.noreply.github.com>
Problem
On an inverted-topology checkout (this box:
origin= NousResearch upstream,myfork= the fork the machine tracks and deploys from), the startup banner perpetually nags:even on a freshly fork-synced tree. Two defects:
_check_via_local_gitcountedHEAD..origin/main(upstream). The checkout is 0 behind its own fork but N behind upstream, so the nag never clears.banner.pypredated the fork-tracking work (#47) and never consumedhermes_cli.fork_tracking.Fix
Wire
_check_via_local_gittofork_tracking.detect_fork_tracking(read-only consumer):HEAD..{cfg.fork_ref}(myfork/main), fetching the fork remote — never origin. Banner reads "N behind your fork" — 0 on a synced checkout, non-zero only on genuine local drift behind the fork. Remedy copy points at the fork-sync workflow, not the upstream update command.origin/main, suggest the upstream update command. PyPI / container / official-remote installs unchanged._check_via_local_gitnow returns(behind, baseline).check_for_updatesthreads the baseline into the 6h.update_checkcache (newbaselinekey) plus a module-level getter, so a fork cache is never read as an upstream cache (or vice-versa) and the render picks the correct remedy.Verification
On the live inverted checkout (origin = NousResearch, myfork = sahilm-ti):
Tests (
tests/hermes_cli/test_update_check.py, 17 pass;tests/test_fork_tracking.py, 16 pass;tests/gateway/test_update_command.py, 35 pass):test_check_via_local_git_fork_tracking_counts_against_fork— countsHEAD..myfork/main, fetchesmyforknotorigin.test_check_via_local_git_fork_tracking_reports_drift— non-zero fork-relative count on drift.test_check_via_local_git_non_fork_unchanged— upstream path invariant (origin/main, fetch origin, baseline=upstream).test_fork_cache_carries_baseline_back/test_check_for_updates_persists_fork_baseline— baseline persisted to and read back from cache.test_check_for_updates_expired_cacheupdated to assert the upstream-path git calls are present rather than an exact subprocess call count (fork-detection adds one leadinggit remote get-url originprobe).ruff checkclean;tyintroduces zero new diagnostics (4 pre-existingbuild_welcome_bannersignature warnings, unrelated, present on base).Scope / non-goals
fork_tracking.py— no change to its merge logic or the cron fork-sync workflow.🤖 Authored by sahilm-ai (AI worker) for kanban task
t_6c2d0631.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes