fix(update): self-heal stale git locks that wedge every update fetch - #86928
Merged
Conversation
…le' on shallow clones Two related failure modes after a crashed/interrupted fetch on a shallow clone (git clone --depth 1 installs): 1. STALE LOCK WEDGES EVERY FETCH. A killed fetch can leave .git/shallow.lock behind; every later 'git fetch' then fails with 'Unable to create .../shallow.lock: File exists'. 'hermes update --check' reported a hard fetch failure, and the passive banner check swallowed the exception and compared stale refs. Add hermes_cli.gitlock.clear_stale_git_locks(), a guarded sweep (age + git-process check so a live fetch is never yanked) wired into the check path, the apply path, and the banner's passive check. 2. SHALLOW TIP-SHA COMPARE FALSE-POSITIVES. On a shallow clone the check cannot count commits, so it compares tip SHAs. Local cherry-picks on top of the remote tip (e.g. re-applied local patches) make HEAD differ from origin/main even though HEAD already contains it — a false 'update available' banner. Add hermes_cli.gitlock.is_ancestor_of_head() and use 'git merge-base --is-ancestor' in the CLI check and banner paths before reporting an update. Mirror in the desktop (update-count.ts gains an isAncestor input; main.ts probes merge-base --is-ancestor). Tests: tests/test_gitlock.py (9) covering stale/young/no-lock/no-repo sweeps and ancestry true/false; update-count.test.ts +3 for the isAncestor path.
…with compare-API status Follow-up on the cherry-picked gitlock work (#80501 by @RGerrish, covering the #75133 / #75168 wedge first reported and fixed by @RelaxJonh): - Drop the PR's ancestor-check halves in banner.py, update-count.ts and main.ts: superseded by the compare-API status recovery that landed in #86257/#86331 (ahead_by == 0 already reports local-ahead as up to date). The salvaged update_cmd.py check path keeps main's compare-API structure instead of the PR's tip-SHA-plus-ancestry print. - Keep and wire clear_stale_git_locks() at the remaining wedge sites the original PR targeted: hermes update apply, hermes update --check, and the passive banner check. - Add the desktop counterpart (electron/gitlock.ts) so checkUpdates() heals the same wedge instead of reporting fetch-failed forever; mirrored age + git-process guards; vitest coverage. E2E verified: real --depth 1 clone with an aged .git/shallow.lock reproduces "Unable to create '.git/shallow.lock': File exists"; clear_stale_git_locks removes it and the fetch succeeds; a fresh lock (in-flight fetch) is preserved.
Contributor
૮ >ﻌ< ა ci reviewran on d2c14bf — style: satisfy desktop eslint (curly braces, import order)
|
This was referenced Aug 15, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hermes update,hermes update --check, the passive banner check, and the desktop update check now self-heal stale.git/shallow.lock(and sibling git lock files) left behind by a crashed/interrupted fetch — previously every later fetch failed withfatal: Unable to create '.git/shallow.lock': File existsforever, which the GUI misreads as a concurrent process and traps the user in an unrecoverable update loop (#75133).Salvages #80501 by @RGerrish (gitlock module + tests, wired at CLI check/apply), credits #75168 by @RelaxJonh as the earliest fix for the same wedge. The original PR's local-ahead ancestry halves were dropped as superseded by the compare-API status recovery from #86257/#86331; a follow-up commit widens the self-heal to the two remaining sibling sites the PR didn't reach on current main (passive banner check, desktop
checkUpdates()).Safety: a lock is removed only when it is older than 10 minutes AND no git process is running — an in-flight fetch is never yanked.
Changes
hermes_cli/gitlock.py(new):clear_stale_git_locks()with age + git-process guards;is_ancestor_of_head()helperhermes_cli/update_cmd.py: self-heal before the--checkfetch and the apply fetchhermes_cli/banner.py: self-heal before the passive check's fetch (a wedged fetch there silently compares stale refs)apps/desktop/electron/gitlock.ts(new) + wiring inmain.tscheckUpdates(): same self-heal, so the desktop stops reportingfetch-failedforevertests/test_gitlock.py(9),apps/desktop/electron/gitlock.test.ts(6)Validation
--depth 1clone + aged shallow.lockInfographic
Omitted with maintainer approval — FAL image generation unavailable (billing balance exhausted); same waiver as #86912.