Skip to content

fix(desktop): don't trust the behind-count on any shallow checkout - #71992

Closed
AlvaroFGarcia wants to merge 1 commit into
NousResearch:mainfrom
AlvaroFGarcia:fix/desktop-shallow-behind-count
Closed

fix(desktop): don't trust the behind-count on any shallow checkout#71992
AlvaroFGarcia wants to merge 1 commit into
NousResearch:mainfrom
AlvaroFGarcia:fix/desktop-shallow-behind-count

Conversation

@AlvaroFGarcia

Copy link
Copy Markdown

Summary

The desktop update indicator can report a five-figure "behind" count on a public install. A real Windows client showed v0.19.0 (+17570) for a checkout that was 143 commits behind main.

#51922 already fixed this class for shallow + no merge-base, but the predicate it introduced does not cover the state an installer clone actually reaches. Probed on the affected machine:

Probe Value
rev-parse --is-shallow-repository true
rev-list --count HEAD 1 (HEAD is in .git/shallow)
rev-list --count origin/main 17571
rev-list HEAD..origin/main --count 17570
merge-base HEAD origin/main HEAD itself
True distance (measured on a full clone of the same repo) 143

17571 - 1 = 17570. That identity is the tell: the count is not finding new commits, it is failing to exclude local history.

Root cause

Because a hermes update fetch deepens origin/main's ancestry while HEAD stays grafted, merge-base HEAD origin/main resolves to HEAD itself. So hasMergeBase is true, the isShallow && !hasMergeBase guard opens, rev-list runs, and the bogus count reaches the UI.

The count is bogus for the same underlying reason in both cases: the graft boundary stops git from walking HEAD's ancestry, so rev-list HEAD..origin/<branch> cannot subtract local history and enumerates nearly the whole remote instead. A merge-base probe cannot separate the safe case from the broken one — shallowness alone is the reliable signal.

The fix

Gate on shallowness alone, which is what the Python side already does — _check_via_local_git in hermes_cli/banner.py and the is_shallow branch of cmd_check_update in hermes_cli/main.py both skip counting on any shallow repo and compare tip SHAs. This aligns the desktop with the CLI rather than leaving a third divergent variant. (That divergence is also why the affected user's CLI banner said "update available" while the desktop screamed five figures.)

Full clones (developers, Docker dev images) keep the exact count path unchanged — including a full clone with genuinely unrelated history, whose local ancestry is walkable and whose count is therefore real. A test pins that case so the fix isn't over-applied.

Dropping hasMergeBase also removes one git merge-base subprocess from every passive update check.

Test plan

  • Fail-before: the added regression reproduces the observed shallow + self-as-merge-base state and asserts 1; against the previous predicate it fails with AssertionError: 17570 !== 1
  • Green-after: all 10 tests in update-count.test.ts pass with the change
  • Full desktop electron vitest project: 727 passing, no new failures (2 pre-existing env-only failures — missing simple-git / node-pty — reproduce identically on untouched main)
  • tsc --noEmit: no diagnostics on either changed file
  • prettier --check: clean
  • Verified end-to-end on the affected machine: after git fetch --unshallow, the count dropped 17570 -> 143, cross-confirmed against an independent full clone of the same repo reporting the same 143

Refs #51922

The desktop update indicator can report a five-figure "behind" count on a
public install: a real Windows client showed `v0.19.0 (+17570)` for a
checkout that was 143 commits behind main.

NousResearch#51922 already fixed this class for `shallow + no merge-base`, but the
predicate it introduced does not cover the state an installer clone
actually reaches. On that machine:

    rev-parse --is-shallow-repository -> true
    rev-list --count HEAD             -> 1        (HEAD is in .git/shallow)
    rev-list --count origin/main      -> 17571
    rev-list HEAD..origin/main --count-> 17570    (= 17571 - 1)
    merge-base HEAD origin/main       -> HEAD itself

Because a `hermes update` fetch deepens `origin/main`'s ancestry while HEAD
stays grafted, `merge-base` resolves to HEAD, so `hasMergeBase` is true, the
`isShallow && !hasMergeBase` guard opens, and the bogus count reaches the UI.

The count is bogus for the same reason in both cases: the graft boundary
stops git from walking HEAD's ancestry, so `rev-list HEAD..origin/<branch>`
cannot exclude local history and enumerates nearly the whole remote instead.
The reported number is `count(origin/branch) - count(HEAD)`, which is an
artifact of truncated history, not a measure of distance. A merge-base probe
cannot separate the safe case from the broken one, so gate on shallowness
alone — which is what `_check_via_local_git` in hermes_cli/banner.py and the
`is_shallow` branch of `cmd_check_update` in hermes_cli/main.py already do.
This aligns the desktop with the CLI instead of leaving a third variant.

Full clones (developers, Docker dev images) keep the exact count path
unchanged, including a full clone with genuinely unrelated history, whose
local ancestry is walkable and whose count is therefore real.

Dropping `hasMergeBase` also removes one `git merge-base` subprocess from
every passive update check.

Test: the added regression reproduces the observed shallow +
self-as-merge-base state and asserts 1 rather than 17570; it fails against
the previous predicate (`17570 !== 1`) and passes with this change.
Verified with the desktop `electron` vitest project (727 passing) and
prettier --check.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #64469 already covers the same all-shallow presence-only count change and adds shallow local-ahead and commit-log handling. This focused repair is a narrower competing option; maintainers can choose the desired scope.

@AlvaroFGarcia
AlvaroFGarcia deleted the fix/desktop-shallow-behind-count branch July 26, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants