Skip to content

fix(update): hard-stop when Windows hermes.exe shim stays locked - #68821

Open
smfworks wants to merge 1 commit into
NousResearch:mainfrom
smfworks:fix/windows-hermes-exe-lock-preflight
Open

fix(update): hard-stop when Windows hermes.exe shim stays locked#68821
smfworks wants to merge 1 commit into
NousResearch:mainfrom
smfworks:fix/windows-hermes-exe-lock-preflight

Conversation

@smfworks

Copy link
Copy Markdown
Contributor

Summary

Fixes the failure mode in #68760: when venv/Scripts/hermes.exe cannot be renamed out of the way, hermes update used to continue into uv/pip, print WinError 32 three times, then fall back to ZIP which hits the same lock.

Preflight concurrent/venv holders already exist; the gap was the post-quarantine path: if rename + restart-schedule leave the shim locked, install must hard-stop with PIDs and remediation — not hope uv succeeds.

Changes

  • _quarantine_running_hermes_exe now returns (moved, blocked)
    • blocked includes failed renames and deferred OS rename locks (path still occupied until restart)
  • _run_quarantined_install raises HermesShimLockedError when blocked is non-empty
    • prints holder PIDs (shim + venv python), taskkill one-liner, restart guidance when applicable
    • does not call uv/pip
  • Optional-extras retry path does not swallow the lock error into more install attempts
  • cmd_update exits 2 on HermesShimLockedError

Test plan

  • Existing concurrent + quarantine suite (26 tests)
  • New: install aborts with no uv call when shim stays locked
  • verify_core_dependencies mocks updated for new return shape

Closes #68760

@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 platform/windows Native Windows-specific behavior or breakage area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 21, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for targeting the post-quarantine path. The defect is still present on current main: hermes_cli/main.py:7759-7789 continues after a reboot-deferred or failed shim quarantine, and _run_quarantined_install() then always invokes uv/pip at hermes_cli/main.py:7858-7863. The active update pipeline reaches that helper through _install_python_dependencies_with_optional_fallback() (hermes_cli/update_cmd.py:3575-3577).

Suggested changes

  • Add a regression test for the reboot_required path: make _schedule_replace_on_reboot() return True, call _run_quarantined_install(), and assert no installer call occurs. The added test covers the unscheduled locked branch, but the PR also changes the deferred-reboot branch.

The update pipeline was mechanically extracted after this branch, so salvage should retain the current hermes_cli.update_cmd import/re-export boundary.

Automated hermes-sweeper review.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 30, 2026
@smfworks
smfworks force-pushed the fix/windows-hermes-exe-lock-preflight branch from 5a9a059 to 5bf3f09 Compare July 30, 2026 13:00
@smfworks

Copy link
Copy Markdown
Contributor Author

Sweeper response — salvageability=high

Head: 5bf3f09f3

  1. _run_quarantined_install hard-stops with HermesShimLockedError when quarantine returns blocked — no uv/pip call.
  2. Added regression for reboot_required path (schedule deferred) in addition to the unscheduled locked branch.
  3. Rebased onto current main; install still flows through the existing main helpers (update pipeline boundary retained).

Verification

pytest tests/hermes_cli/test_update_concurrent_quarantine.py \
       tests/hermes_cli/test_verify_core_dependencies.py -q
13 passed
ruff: clean

Rebased on current upstream/main.

@smfworks

Copy link
Copy Markdown
Contributor Author

Verification Summary (adversarial 6-check — PASS)

Head: 5bf3f09f3

1–6 PASS. Locked-shim hard-stop + reboot_required regression; no uv/pip install in that path.
Tests: 13 passed

@smfworks

smfworks commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Stewardship ping — verified this fix is still needed on current main:

  • Issue hermes.exe locked during hermes update on Windows (WinError 32) #68760 is still open (hermes.exe locked during hermes update on Windows — WinError 32)
  • No hard-stop on locked shim found in update_cmd.py on main — the post-quarantine path still continues into uv/pip even when the shim stays locked
  • Sweeper previously marked salvageability=high

The PR adds HermesShimLockedError hard-stop when quarantine returns blocked, with PIDs and remediation steps. No uv/pip install attempt in that path. Adversarial 6-check passed (13 tests). Ready for review or merge.

@smfworks
smfworks force-pushed the fix/windows-hermes-exe-lock-preflight branch from 5bf3f09 to f50bcf4 Compare August 6, 2026 05:46
@smfworks

smfworks commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Stewardship follow-up — rebased onto current main (6564f319)

Cherry-pick clean, 3 files changed, 258 insertions, 30 deletions.

Sweeper marked salvageability=high. The fix hard-stops with HermesShimLockedError when quarantine returns blocked — no uv/pip call. The regression covers both the reboot_required path (schedule deferred) and the unscheduled locked branch.

On main, hermes_cli/main.py:8124 shows the shim-locked message, but there's no hard-stop before the uv install attempt. The fix is still needed.

Test status: Pass on rebased branch.

Ready for review.

@smfworks
smfworks force-pushed the fix/windows-hermes-exe-lock-preflight branch from f50bcf4 to 3629782 Compare August 8, 2026 09:31
When quarantine cannot free venv entry-point shims, abort install with
holder PIDs and remediation instead of continuing into WinError 32 x3
and a doomed ZIP fallback (NousResearch#68760).

Deferred OS rename paths also block install until the machine is
restarted — the shim path remains locked for uv.
@smfworks
smfworks force-pushed the fix/windows-hermes-exe-lock-preflight branch from 3629782 to 2281496 Compare August 14, 2026 06:20
@smfworks

Copy link
Copy Markdown
Contributor Author

Restored source + tests onto current main. Focused Windows hermes.exe lock hard-stop. Previous tip had accumulated unrelated fleet files.

adamcap926 added a commit to adamcap926/hermes-agent that referenced this pull request Aug 14, 2026
On Windows `hermes update` can never complete. `hermes` is launched
through a distlib console-script launcher, which is not a thin redirect:
it spawns `venv\Scripts\python.exe` as a child, hands it the launcher's
own path as the script, and blocks. Every invocation is two processes:

    hermes.exe (pid A, holds Scripts\hermes.exe as its running image)
      \_ python.exe (pid B, runs hermes_cli.main)

The update ends in `uv pip install -e .`, which rewrites the console-
script shims -- including the one pid A has mapped. Windows refuses to
replace a running image, so uv fails with "The process cannot access the
file because it is being used by another process. (os error 32)". The
git path fails, the ZIP fallback fails identically, and no retry can
succeed: nothing pid B does releases the lock, because the holder is its
parent.

Remove the hazard rather than race it. Before the update touches
anything, re-launch it as a detached `python -m hermes_cli.main`
grandchild and let pids B and A exit. The grandchild waits for the
launcher to disappear, then updates against an ordinary unlocked file:

    hermes.exe (A) --> python.exe (B) --> python.exe (C, detached)
         exit             exit              waits for A, then updates

Hooked into cmd_update after --check (which installs nothing) and before
the update lock is acquired: a trampolining parent that had already
written the lock marker would release it moments later while the
grandchild ran unlocked, since the grandchild's acquire() sees its
still-live parent as an ancestor and would run under a claim about to
vanish.

stdin is DEVNULL so the detached updater cannot race the reclaimed shell
for keystrokes. That also selects the existing non-interactive path, so
local changes follow updates.non_interactive_local_changes (default
stash) rather than prompting where nobody can answer.

Off Windows maybe_trampoline() returns False immediately and behavior is
unchanged; POSIX replaces a running executable's inode atomically.

Verified on Windows 11 / Python 3.11.15 / uv, non-elevated: before, four
consecutive failed updates; after, the launcher exits, the grandchild
completes the update, and no os error 32 occurs on either path.

Scoped deliberately to the trampoline. The residual case where another
process holds a shim, and cleanup of the reboot-deferred rename entries
that case queues, are addressed by NousResearch#68821 and NousResearch#85942 respectively and
are not duplicated here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGLv7LyNrpaPm4ciGqhyxD
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 P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hermes.exe locked during hermes update on Windows (WinError 32)

3 participants