Skip to content

fix(updater): release self-held lock during Windows desktop update - #76071

Closed
RelaxJonh wants to merge 1 commit into
NousResearch:mainfrom
RelaxJonh:fix/windows-update-self-lock
Closed

fix(updater): release self-held lock during Windows desktop update#76071
RelaxJonh wants to merge 1 commit into
NousResearch:mainfrom
RelaxJonh:fix/windows-update-self-lock

Conversation

@RelaxJonh

Copy link
Copy Markdown
Contributor

Fixes #75879

Windows desktop update self-locks when a legacy hermes-setup.exe
(predating the HANDOFF_PID_ENV export) sits behind the hermes.exe
Windows launcher shim. The Python child's direct parent is the shim,
not the lock-owning updater, so the ancestor check cannot see the
lock owner through os.getppid() alone.

Changes

hermes_cli/update_lock.py (1 file, +36 −2):

  1. _is_ancestor_pid — limit psutil walk to nearest 3 ancestors
    (covers hermes-setup.exe → hermes.exe → python.exe); add
    os.getppid() fallback when psutil is not installed.
  2. acquire — separate explicit-handoff and legacy-ancestor
    paths so a present-but-wrong HANDOFF_PID is rejected immediately
    without falling through to the ancestor check.

Validation (from issue report)

  • py_compile of the patched module: ✓
  • Simulated hermes-setup.exe → hermes.exe → python.exe handoff: accepted ✓
  • Marker owner at the fourth ancestor: rejected ✓
  • Present but incorrect HERMES_UPDATE_HANDOFF_PID: rejected ✓
  • Real staged updater end-to-end: update stage state=Succeeded

Legacy hermes-setup.exe binaries that predate the HANDOFF_PID_ENV export
spawn hermes.exe (the Windows launcher shim) as an intermediate process
before python.exe.  The Python child's direct parent is hermes.exe, not
the lock-owning hermes-setup.exe, so the existing ancestor check that
walked all parents with psutil was correct in principle but had two gaps:

1. When psutil was unavailable the check silently returned False with no
   fallback, leaving the self-lock in place on installs without psutil.

2. The acquire() logic used a flat OR: a present-but-wrong HANDOFF_PID
   still fell through to the ancestor check, which could accept a marker
   owned by an unrelated ancestor process.

Fixes NousResearch#75879

- _is_ancestor_pid: limit psutil walk to nearest 3 ancestors; add
  os.getppid() fallback when psutil is not installed.
- acquire: separate explicit-handoff and legacy-ancestor paths so a
  wrong HANDOFF_PID is rejected immediately without falling through.
@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 comp/desktop Electron desktop app (apps/desktop/*) 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 Aug 1, 2026
@teknium1

teknium1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for the focused Windows compatibility investigation. This is an automated hermes-sweeper review; the reported legacy updater self-lock is already handled on current main.

  • ed77a347307e2e9c7f967ae146c4d3148a63433d introduced the current cross-platform ancestor handoff using psutil.Process().parents().
  • hermes_cli/update_lock.py:140-160 walks the parent chain, so it already recognizes the reported hermes-setup.exe → hermes.exe → python.exe topology.
  • hermes_cli/update_lock.py:245-250 accepts a live marker owned by that ancestor, rather than treating it as a competing update.
  • psutil is a required cross-platform dependency (pyproject.toml:94-99).

Closing as implemented on main.

@teknium1 teknium1 closed this Aug 1, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Aug 1, 2026
@abcultima

Copy link
Copy Markdown

@teknium1 Thank you for the clarification that the legacy self-lock reported by this PR is already implemented on main. I retested on the affected native Windows Desktop installation after a successful update, and found that the original self-lock is no longer the active failure mode. There is, however, a separate, reproducible preflight blocker that occurs immediately after the successful update restarts Gateway.

What happened in this retest

  1. The first Desktop update after a Windows reboot completed successfully. The bootstrap log records Update complete! and update stage state=Succeeded at 2026-08-01T11:14:19Z.
  2. As part of that successful update, the bootstrap explicitly restarted the Windows Gateway:
Starting Windows gateway after update (PID 14388)
  1. Without rebooting the PC, a second click on Desktop Update failed before the updater itself could run. Desktop reported:
[updates] venv-blocked: 1 process(es) hold the install
[updates] error: Update aborted: another Hermes process is using this installation.
PID 8404 python.exe %LOCALAPPDATA%\\hermes\\hermes-agent\\venv\\Scripts\\python.exe F:\\uo\\Stealth_MCP\\server.py
  1. A read-only live process inspection confirmed that this was not an unrelated Python process and not an update-lock/self-handoff failure. The live chain was:
PID 8404   %LOCALAPPDATA%\\hermes\\hermes-agent\\venv\\Scripts\\python.exe F:\\uo\\Stealth_MCP\\server.py
  -> PID 10836  ...\\uv\\python.exe -m hermes_cli.main gateway run
    -> PID 14388  %LOCALAPPDATA%\\hermes\\hermes-agent\\venv\\Scripts\\python.exe -m hermes_cli.main gateway run

So Stealth_MCP/server.py is a Gateway-managed child/helper using the Hermes venv.

Why current Desktop preflight still blocks it

The freshly updated install's read-only scanner invocation:

& "$env:LOCALAPPDATA\\hermes\\hermes-agent\\venv\\Scripts\\python.exe" -m hermes_cli._scan_venv_blockers

returned:

{
  "ok": true,
  "blocked": true,
  "processes": [
    {
      "pid": 8404,
      "name": "python.exe",
      "cmdline": "%LOCALAPPDATA%\\hermes\\hermes-agent\\venv\\Scripts\\python.exe F:\\uo\\Stealth_MCP\\server.py"
    }
  ],
  "pausable_gateways": 1
}

This is useful evidence that the direct gateway run process is now recognized as pausable/exempted, but its child helper is still reported as a fatal venv holder. The scanner currently exempts a process only when that process's own command line matches canonical gateway run; Stealth_MCP/server.py does not match even though its parent chain proves the Gateway relationship. Consequently Desktop aborts before it launches hermes-setup / hermes update, so the CLI's downstream Gateway-pause logic never has a chance to coordinate this helper.

The SIGTERM / backend exited before it became ready screen seen after the error is downstream of this aborted Desktop update handoff, not the primary cause.

Scope relative to #76071

I agree that the legacy hermes-setup.exe -> hermes.exe -> python.exe self-lock described in this PR is a separate issue and appears handled on current main. This new evidence is instead the Windows Gateway-child/MCP-helper venv-holder condition discussed in #61514. It explains the deterministic pattern: first update after reboot succeeds; that update restarts Gateway; Gateway starts Stealth_MCP; the next Desktop update is blocked by that helper.

Conservative upstream direction requested

Could maintainers confirm whether this helper topology is intended to be covered by the Gateway update work? A safe approach would be to extend Desktop preflight and the updater pause path only for helpers whose ownership is provable, for example:

  • Windows only, and only when a venv helper has a canonical gateway run ancestor within a short bounded parent chain.
  • Snapshot those proven helper PIDs before pausing Gateway.
  • Stop/reap only those snapshot-backed helpers along with their confirmed Gateway process tree during the update.
  • Re-scan fail-closed: any unproven, orphaned, protected, or unrelated venv Python process remains a hard block.
  • Let normal Gateway startup recreate its managed helper afterward.

This would avoid a broad --force-kill policy while fixing the exact post-update lifecycle loop reproduced above. I am not claiming the local ancestry heuristic is a complete permanent design; explicit Gateway-owned helper lifecycle/process-group metadata would be a stronger long-term contract.

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 platform/windows Native Windows-specific behavior or breakage sweeper:implemented-on-main Sweeper: behavior already present on current main 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.

Windows desktop update self-lock with legacy hermes-setup.exe behind hermes.exe launcher shim

4 participants