Skip to content

fix(update): self-heal venv after failed lazy backend refresh (salvage #58004) - #69867

Merged
teknium1 merged 5 commits into
mainfrom
hermes/hermes-311ee5c1
Jul 23, 2026
Merged

fix(update): self-heal venv after failed lazy backend refresh (salvage #58004)#69867
teknium1 merged 5 commits into
mainfrom
hermes/hermes-311ee5c1

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

hermes update now self-heals a venv corrupted by a failed lazy backend refresh — including the worst case where the corruption prevents hermes itself from launching. Salvages PR #58004 by @HexLab98 onto current main and closes the final review blocker: recovery previously lived inside hermes_cli.main, whose own module-level imports (dotenv via env_loader, yaml via config) crash first in exactly the failure state the recovery exists for (#57828).

Changes

Cherry-picked from #58004 (@HexLab98, authorship preserved):

  • Upgrade pip before lazy backend refreshes (stale pip source-build failures were the corruption trigger)
  • Import-probe repair: after a failed lazy uv pip install, probe 7 core packages via real imports (metadata can survive while .py files are wiped) and --force-reinstall broken ones with pyproject pins
  • Two-marker lifecycle: .update-incomplete (interrupted core .[all] install → full quarantined reinstall) split from .lazy-refresh-incomplete (refresh phase → package-only import repair); narrow probes can never clear the core marker
  • Indeterminate probes (unresolvable venv Python, probe subprocess failure) are never treated as healthy
  • Package-only installs bypass the shim-quarantine path so Windows hermes.exe launchers survive pip upgrades
  • Windows hermes.exe-ancestor launches get package-only first aid, with the core marker retained until the full reinstall succeeds

Follow-up (ours):

  • hermes_cli/_early_recovery.py — stdlib-only bootstrap repair invoked at the very top of main.py, before any third-party import. Probes the fragile core packages in-process, force-reinstalls broken ones with pyproject pins, shares the single-flight recovery lock, and never clears markers (the confirmed lifecycle stays with _recover_from_interrupted_install(), which runs right after import succeeds)
  • Probe/repair tables moved to one canonical home in _early_recovery, re-exported by main.py so the two layers cannot drift
  • Manual --force-reinstall fallback commands now print pinned specs via _lazy_refresh_repair_specs() instead of bare names

Validation

Before After
Failed lazy refresh wipes dotenv hermes crashes importing main.py; markers unreachable; every update fails Early bootstrap repairs before import; launch proceeds
Interrupted core install Marker cleared by 7-package probe (false-healthy) Core marker only cleared by confirmed full reinstall
Probe can't run Treated as healthy, marker cleared Indeterminate — marker retained
  • scripts/run_tests.sh — 78/78 across test_early_recovery.py (13, new), test_lazy_refresh_venv_repair.py (15), test_update_interrupted_recovery.py (16), test_update_autostash.py (34)
  • Entry-point lifecycle test proves a broken dotenv crashes the hermes_cli.main import without repair and imports cleanly with it; a stdlib-only import guard proves _early_recovery loads on a corrupted venv
  • Live E2E: real venv, real pip, dotenv import files deleted with metadata intact, marker present → early recovery detected + force-reinstalled python-dotenv==1.2.2, import restored, marker preserved for the full recovery pass

Closes #58004 (salvaged, @HexLab98's commits cherry-picked with authorship preserved). Closes #58246 (superseded — its metadata-only verifier misses the metadata-survives/imports-gone case this PR's import probes catch; thanks @tianma-if). Fixes #57828.

Infographic

self-healing-venv-recovery

HexLab98 and others added 5 commits July 22, 2026 21:44
Upgrade pip before lazy refreshes, probe core imports when a lazy
install fails, force-reinstall corrupted packages with pyproject pins,
use package-only install (no shim quarantine) for repair, and keep the
.update-incomplete marker until refresh/repair succeeds (#57828).
Add repair/probe/quarantine regression tests and update autostash mocks
for the new lazy-refresh signature.
Keep .update-incomplete across normal hermes.exe launches, heal via
package-only import probes first, and only clear the marker after repair
succeeds (#57828 / #58004 review).
Keep .update-incomplete for full .[all] recovery only. Lazy refresh uses
.lazy-refresh-incomplete and clears only after confirmed import probes;
unavailable probes are indeterminate, not healthy (#58004 review).
The hermes console entry point is hermes_cli.main:main, and main.py imports
dotenv (via env_loader) and yaml (via config) at module level. In the #57828
failure state — a failed lazy backend refresh wiping a core package's import
files while metadata survives — a normal launch crashed while importing
main.py, before _recover_from_interrupted_install() and the recovery markers
from PR #58004 could act.

- hermes_cli/_early_recovery.py: stdlib-only bootstrap repair invoked at the
  very top of main.py, before any third-party import. Probes the fragile
  core packages via real imports, force-reinstalls broken ones using the
  pyproject.toml pins, shares main.py's single-flight recovery lock, and
  never clears markers (the confirmed lifecycle stays with the full recovery
  path in main.py).
- Probe/repair tables now have one canonical home in _early_recovery, reused
  by main.py so the two layers cannot drift.
- Manual --force-reinstall fallback commands now print pinned specs via
  _lazy_refresh_repair_specs() instead of bare package names.
- tests: entry-point lifecycle coverage proving a broken dotenv import
  crashes main.py without repair and imports cleanly with it, a stdlib-only
  import guard for _early_recovery, and unit coverage for marker gating,
  lock single-flight, pinned specs, and marker preservation.
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 2d248ac

all good!

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor platform/windows Native Windows-specific behavior or breakage sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 23, 2026
@teknium1
teknium1 merged commit 509960e into main Jul 23, 2026
39 checks passed
@teknium1
teknium1 deleted the hermes/hermes-311ee5c1 branch July 23, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage 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.

[Bug]: Lazy backend refresh failures can corrupt venv, and update has no self-healing recovery

3 participants