Repository navigation
fix(desktop): don't dead-end the update on ledgered manual serve blockers - #98350
liuhao1024 wants to merge 2 commits into
Conversation
…kers The Desktop Windows preflight reported every 'hermes serve'/'dashboard' holder as a venv blocker, aborting the hand-off before hermes-setup could spawn the updater — yet the updater's venv guard has a rung (NousResearch#63206) that stops exactly the MANUAL serve/dashboard backends (spawn-ledger positive identity for this install, spawner provably not alive) and relaunches them on their recorded endpoints after the update. The same dead-end shape the gateway exemption fixed, left unfixed for serves (NousResearch#98336). Mirror the gateway exemption: exempt holders the canonical matcher update_cmd._ledger_manual_serve_holders vouches for, so the preflight, the stop rung, and the relauncher share one parser. Desktop-owned serves (live spawner), unledgered serves, and foreign installs keep blocking; an import failure reads as no manual serves (pre-exemption fail-closed).
ReviewDelegating to the canonical ledger matcher is the right direction: the scanner, updater stop rung, and relauncher should share one install-scoped identity rather than maintain independent command-line heuristics. The fail-closed behavior for matcher import failure also preserves the safer pre-exemption path. Suggested contract
Regression matrix
The handbook's Windows update-boundary guide covers the same process-identity, lock-evidence, and rollback requirements. |
…fixture Address review on NousResearch#98350: report structured ledger identity (pid/purpose/ port, never argv) for exempted manual serves so the scan result explains its decision without echoing command lines, and pin in the TypeScript parser tests that the diagnostic fields stay non-authoritative while blocked/processes consistency is still enforced.
|
Thanks @liyangbing for the thorough review. Pushed Done in this push (scanner scope)
Satisfied by the canonical delegation
Out of scope for this scanner mirror (owned by the #63206 rung and its tests): stop/relaunch idempotency and resumable-state semantics, plus the Windows-quoting/rollback rows of the matrix — those live in the updater ( |
|
Status note (not a verdict): this PR is the scanner-side mirror of the updater's #63206 ledgered-manual-serve rung, targeting #98336. That serve/ Unrelated defects in the same family were resolved separately: scan timeout/perf via #99674 (salvage of #75570), truncated-cmdline classification already on main ( |
|
Acknowledged — holding off on any rebase until the #98336/#81774 consolidation lands. Keeping this PR open as-is for the consolidating maintainer; happy to rework on top of the consolidated |
|
Wave-3 salvage status: the surviving half of this PR now rides in #100124 with your authorship preserved (commit authored as liuhao1024). What survived vs. main: the exemption itself landed independently via #99724 ( This PR will be closed with credit once #100124 merges. Thanks for the careful canonical-matcher delegation design — it shaped the consolidated implementation. |
…lockers The Desktop venv-blocker scan (since #99724) defers ledger-verified serve/dashboard holders to the CLI updater's stop+relaunch rungs, but the scan output only carried an opaque deferred_backends count — nothing explained WHICH holders the deferral consumed or why they vanished from processes. Add sanitized decision evidence (#98350): deferred_backend_evidence lists structured ledger identity only (pid, purpose, recorded port) — never the command line, which can carry tokens or private endpoints. Adds a desktop parser contract fixture proving the consumer tolerates the diagnostics while keeping blocked/processes authoritative. Salvaged from PR #98350; the exemption half of that PR was independently consolidated on main via #99724 (_is_updater_owned_backend).
|
Thanks @liuhao1024 — landed on main via #100124. The exemption half of this was absorbed earlier by #99724 (updater-owned backend classification incl. manual serves), and #100124 carried your surviving evidence half (deferred_backend_evidence: pid/purpose/port, never argv) authored under your name. Closing as superseded by the merged salvages. |
Desktop runs the bundled carrier copy of the venv-blocker scanner with `python -I`, never hermes_cli/_scan_venv_blockers.py. The upstream rebase added `deferred_backend_evidence` (NousResearch#98350) to the module and to the exact-key parser, the carrier never got it, and every "Update now" click aborted with "Desktop could not verify the Hermes installation is free" while zero holders existed (7 aborts on 2026-09-02, desktop.log: `scanner envelope fields are invalid`). A second, latent drift hid behind the pre-scan kill-all: both scanner copies attach `parent_pid` to pausable-gateway records, but the parser only accepted it on generic records, so any scan taken while a gateway was alive was rejected with `pausable gateway identity is invalid`. - carrier emits `deferred_backend_evidence: []` - parser accepts `parent_pid` on gateway records (still a positive integer) - the carrier test now round-trips real scanner stdout through parseVenvBlockerScanOutput, the contract the preflight consumes - parser tests pin the gateway parent_pid case Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Desktop runs the bundled carrier copy of the venv-blocker scanner with `python -I`, never hermes_cli/_scan_venv_blockers.py. The upstream rebase added `deferred_backend_evidence` (NousResearch#98350) to the module and to the exact-key parser, the carrier never got it, and every "Update now" click aborted with "Desktop could not verify the Hermes installation is free" while zero holders existed (7 aborts on 2026-09-02, desktop.log: `scanner envelope fields are invalid`). A second, latent drift hid behind the pre-scan kill-all: both scanner copies attach `parent_pid` to pausable-gateway records, but the parser only accepted it on generic records, so any scan taken while a gateway was alive was rejected with `pausable gateway identity is invalid`. - carrier emits `deferred_backend_evidence: []` - parser accepts `parent_pid` on gateway records (still a positive integer) - the carrier test now round-trips real scanner stdout through parseVenvBlockerScanOutput, the contract the preflight consumes - parser tests pin the gateway parent_pid case Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lockers The Desktop venv-blocker scan (since NousResearch#99724) defers ledger-verified serve/dashboard holders to the CLI updater's stop+relaunch rungs, but the scan output only carried an opaque deferred_backends count — nothing explained WHICH holders the deferral consumed or why they vanished from processes. Add sanitized decision evidence (NousResearch#98350): deferred_backend_evidence lists structured ledger identity only (pid, purpose, recorded port) — never the command line, which can carry tokens or private endpoints. Adds a desktop parser contract fixture proving the consumer tolerates the diagnostics while keeping blocked/processes authoritative. Salvaged from PR NousResearch#98350; the exemption half of that PR was independently consolidated on main via NousResearch#99724 (_is_updater_owned_backend).
Desktop runs the bundled carrier copy of the venv-blocker scanner with `python -I`, never hermes_cli/_scan_venv_blockers.py. The upstream rebase added `deferred_backend_evidence` (NousResearch#98350) to the module and to the exact-key parser, the carrier never got it, and every "Update now" click aborted with "Desktop could not verify the Hermes installation is free" while zero holders existed (7 aborts on 2026-09-02, desktop.log: `scanner envelope fields are invalid`). A second, latent drift hid behind the pre-scan kill-all: both scanner copies attach `parent_pid` to pausable-gateway records, but the parser only accepted it on generic records, so any scan taken while a gateway was alive was rejected with `pausable gateway identity is invalid`. - carrier emits `deferred_backend_evidence: []` - parser accepts `parent_pid` on gateway records (still a positive integer) - the carrier test now round-trips real scanner stdout through parseVenvBlockerScanOutput, the contract the preflight consumes - parser tests pin the gateway parent_pid case Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
What does this PR do?
On Windows, the Desktop update preflight (
hermes_cli._scan_venv_blockers) reports everyhermes serve/dashboardholder as a venv blocker, so the hand-off aborts withvenv-blockedbeforehermes-setupever spawns the updater — even though the CLI updater's venv guard has a rung (#63206) that stops exactly the MANUAL serve/dashboard backends (spawn-ledger positive identity for this install, spawner provably not alive) and relaunches them on their recorded endpoints after the update. This is the same dead-end shape the gateway exemption (_is_pausable_gateway) already fixed forgateway run; this PR closes the gap for manual serves (#98336).The fix mirrors the gateway exemption: holders vouched for by the canonical matcher
update_cmd._ledger_manual_serve_holdersare exempted from the blocker list (counted in a new diagnosticledgered_manual_servesfield). Delegating to that matcher — rather than a cmdline regex — keeps the preflight exemption, the updater's stop rung, and the relauncher on one parser, and inherits its safety properties: install-scoped ledger identity ((pid, create_time)for THIS install), spawner-liveness check, and structured relaunch.Deliberately NOT exempted (fail-closed, unchanged behavior):
Related Issue
Fixes #98336
Type of Change
Changes Made
hermes_cli/_scan_venv_blockers.py: added_ledgered_manual_serve_pids(), which delegates to the canonical ledger matcher (import failure → empty set, i.e. fail-closed);main()now skips those PIDs after the gateway exemption and reports aledgered_manual_servesdiagnostic count (mirroringpausable_gateways).tests/hermes_cli/test_scan_venv_blockers.py: three regression tests — ledgered manual serve scans clear; a ledgered serve alongside an unledgered/Desktop-owned serve still blocks on the latter only; a matcher failure keeps the serve blocking (fail-closed).How to Test
python -m hermes_cli._scan_venv_blockers→ Observed result:{"ok": true, "blocked": false, "processes": [], "pausable_gateways": 0, "ledgered_manual_serves": 0}, exit 0 — new field present, import chain intact.ruff checkon both changed files → all checks passed.The TS consumer (
apps/desktop/electron/venv-blocker-scan.ts) needs no change:parseVenvBlockerScanOutputignores unknown top-level fields, andblocked/processesconsistency is produced by the Python side.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/A (docstring on the new helper documents the contract)cli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A