Skip to content

fix(update): self-heal stale git locks that wedge every update fetch - #86928

Merged
teknium1 merged 4 commits into
mainfrom
fix/stale-git-lock-selfheal
Aug 15, 2026
Merged

fix(update): self-heal stale git locks that wedge every update fetch#86928
teknium1 merged 4 commits into
mainfrom
fix/stale-git-lock-selfheal

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

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 with fatal: Unable to create '.git/shallow.lock': File exists forever, 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() helper
  • hermes_cli/update_cmd.py: self-heal before the --check fetch and the apply fetch
  • hermes_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 in main.ts checkUpdates(): same self-heal, so the desktop stops reporting fetch-failed forever
  • Tests: tests/test_gitlock.py (9), apps/desktop/electron/gitlock.test.ts (6)

Validation

Check Result
E2E: real --depth 1 clone + aged shallow.lock wedge reproduces; heal removes lock; fetch succeeds
E2E: fresh lock (in-flight fetch) preserved
tests/test_gitlock.py + update-check suites 11 + 29 pass
desktop vitest (gitlock + update-count) 25 pass
desktop typecheck (all 3 tsconfigs) clean
ruff clean

Infographic

Omitted with maintainer approval — FAL image generation unavailable (billing balance exhausted); same waiver as #86912.

RGerrish and others added 3 commits August 15, 2026 04:22
…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.
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on d2c14bf — style: satisfy desktop eslint (curly braces, import order)

⚠️ Warnings

OSV vulnerability scan · View job

5 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 3m43s vs 3m25s (+8.8%). 25 job(s) slower, 12 faster,

  • JS & TS checks / apps/desktop / check:test:ui:shard-2of3: +34.0s
  • Python tests / Run tests slice 3/12: -25.0s
  • Python tests / Run tests slice 1/12: +22.0s
  • JS & TS checks / apps/desktop / check:lint: +17.0s
  • Python tests / Run tests slice 12/12: +16.0s

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard 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 P2 Medium — degraded but workaround exists labels Aug 15, 2026
@teknium1
teknium1 merged commit bda83a4 into main Aug 15, 2026
61 checks passed
@teknium1
teknium1 deleted the fix/stale-git-lock-selfheal branch August 15, 2026 11:33
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/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists 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.

3 participants