fix(windows): Split hermes update into two-phase pull + post-pull with re-exec - #39325
ethernet8023 wants to merge 7 commits into
Conversation
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-argument-type |
1 |
First entries
hermes_cli/main.py:10790: [invalid-argument-type] invalid-argument-type: Argument to function `restore_cron_jobs_if_emptied` is incorrect: Expected `str`, found `Any | None`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-argument-type |
1 |
First entries
hermes_cli/main.py:10880: [invalid-argument-type] invalid-argument-type: Argument to function `restore_cron_jobs_if_emptied` is incorrect: Expected `str`, found `None | str`
Unchanged: 5101 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
2c9303b to
5cc1a91
Compare
…h re-exec
_cmd_update_impl (monolith ~1350 lines) is gone. The update flow is now:
Phase 1 (_cmd_update_pull_new_version):
- concurrent guard, backup, git pull / ZIP download+extract
- stash handling, syntax guard, rollback, bytecode clear
Phase 2 (_cmd_update_post_pull):
- pip install (managed-uv), node deps, web UI, desktop rebuild
- skills sync, config migration, gateway restart, cleanup
Between phases, _reexec_for_post_pull replaces the process:
- POSIX: os.execvp (true exec, same PID, fresh sys.modules)
- Windows: subprocess.run relay (parent stays alive so bootstrap
installer's child.wait() sees the real exit code)
Hidden --post-pull flag routes directly to phase 2.
--pre-update-snapshot carries snapshot ID across exec boundary.
Pip self-update is removed entirely. pip/uv/pipx installs now error
with recommended_update_command guidance, same as managed installs.
Also allow uv to come from termux.
Test updates:
- _inline_post_pull autouse fixture in test_cmd_update,
test_update_autostash, test_update_concurrent_quarantine
(patches _reexec_for_post_pull to call _cmd_update_post_pull
in-process, preventing os.execvp from nuking pytest)
- test_uv_tool_update: removed _cmd_update_pip tests (function
deleted), kept is_uv_tool_install + recommendation helpers
- test_update_zip_symlink_reject: renamed imports to match
_update_files_via_zip (now a thin delegation wrapper)
5cc1a91 to
6921469
Compare
e2f6767 to
55879eb
Compare
|
Confirming this PR fixes a real, reproducible crash — sharing the root-cause analysis in case it's useful for the description / a linked issue (the "Fixes #" is currently blank). Symptom — Root cause — exactly the stale-bytecode-after-pull race this PR targets:
So the breaking hop is any update that crosses #37660 (tuple → bare path) while still executing the pre-split updater. The two-phase pull + (Inherent caveat, not a critique: re-exec can't rescue the first hop that installs it, since the crashing run is still the old monolith. After this lands, future skews are prevented.) Repro on the affected window: be on a commit ≤ #37660's parent, then |
|
Follow-up — a related sharp edge in the shared-clone / desktop interaction that this PR's Observed: after a CLI Why: the CLI and the desktop drive the same
So the two updaters ping-pong the single clone: CLI pushes it to This isn't a crash, but it's a confusing loop for anyone who uses both the CLI and the desktop. Two things that might help, if in scope here:
Happy to file this as its own issue if you'd prefer to keep the PR focused — just flagging since |
|
too complicated |
What does this PR do?
Splits
hermes updateinto a two-phase pull + post-pull flow with process re-exec, and fixes Windows installer robustness when.gitis missing or the clone is partial.The old monolith (
_cmd_update_impl, ~1350 lines) ran post-pull steps (pip install, node deps, skills sync, gateway restart, etc.) under stale bytecode aftergit pullor ZIP extraction overwrote source files on disk. This causedImportErroron gateway restart and subtle drift bugs becausesys.modules/__pycache__still held the old code.Phase 1 (
_cmd_update_pull_new_version): concurrent guard, backup, git pull / ZIP download+extract, stash handling, syntax guard, rollback, bytecode clear. ReturnsTrueif new code landed.Phase 2 (
_cmd_update_post_pull): pip install (managed-uv), node deps, web UI, desktop rebuild, skills sync, config migration, gateway restart, cleanup — now running under freshly-exec'd Python with cleansys.modules.Between phases,
_reexec_for_post_pullreplaces the process:os.execvp(true exec, same PID, fresh modules)subprocess.runrelay (parent stays alive so bootstrap installer'schild.wait()sees the real exit code)Additional changes:
recommended_update_commandguidance (same as managed installs).install.ps1) now verifies remote reachability (git ls-remote) before assuming a clone is valid.update.rs) comment updated to reflect new file path (hermes_cli/update.py).Related Issue
Fixes #
Type of Change
Changes Made
hermes_cli/main.py— Split_cmd_update_implinto_cmd_update_pull_new_version+_cmd_update_post_pull+_reexec_for_post_pull; rename_update_files_via_zip→_download_and_extract_zip(thin delegation wrapper); remove_cmd_update_pip; add--post-pull/--pre-update-snapshothidden args; pip/uv-tool install guardhermes_cli/managed_uv.py— Allow uv from termuxapps/bootstrap-installer/src-tauri/src/update.rs— Comment path fix (main.py → update.py)scripts/install.ps1— Addgit ls-remotereachability check before deeming clone validtests/hermes_cli/test_cmd_update.py—_inline_post_pullautouse fixture; updated mocks for two-phase flowtests/hermes_cli/test_update_autostash.py—_inline_post_pullautouse fixturetests/hermes_cli/test_update_concurrent_quarantine.py—_inline_post_pullautouse fixturetests/hermes_cli/test_managed_uv.py— Updated for managed-uv changestests/hermes_cli/test_uv_tool_update.py— Removed_cmd_update_piptests (function deleted); keptis_uv_tool_install+ recommendation helperstests/hermes_cli/test_update_zip_symlink_reject.py— Renamed imports to match_update_files_via_zipdelegation wrappertests/hermes_cli/test_update_venv_ready.py— Removed (covered by new two-phase flow)PLAN.md— Design document for the two-phase refactor (added)How to Test
hermes updateon a POSIX system — should complete two-phase flow with re-exec, same visible output as beforehermes updateon a Windows system without.git— should use ZIP fallback with subprocess relayhermes updatefrom a pip/uv-tool install — should error with recommended update commandpytest tests/hermes_cli/test_cmd_update.py tests/hermes_cli/test_update_autostash.py tests/hermes_cli/test_update_concurrent_quarantine.py tests/hermes_cli/test_uv_tool_update.py tests/hermes_cli/test_managed_uv.py -qchild.wait()still gets correct exit codes on WindowsChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A