Skip to content

fix(desktop): make Windows package rebuild transactional and self-healing - #91079

Draft
andrexibiza wants to merge 22 commits into
NousResearch:mainfrom
andrexibiza:fix/windows-desktop-pack-transaction
Draft

fix(desktop): make Windows package rebuild transactional and self-healing#91079
andrexibiza wants to merge 22 commits into
NousResearch:mainfrom
andrexibiza:fix/windows-desktop-pack-transaction

Conversation

@andrexibiza

@andrexibiza andrexibiza commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Current source of truth — 2026-08-27 10:22 CDT

Live submitted object:

  • head: f84cecb3a630bd3dd07791d4e00ea94c06d5ba82
  • state: open, draft; current carrier is 18 changed paths / 22 commits
  • repository main at reconciliation: 42ac29eacc4d743ed2df7db0f886b99111d9e68b
  • exact-head workflows: no current execution evidence certifies this submitted object
  • review threads: none unresolved

This carrier is not the final product object. Current main has materially changed Desktop ownership/routing, updater, process, package, recovery, and backend-readiness surfaces. Composition must be semantic; do not overlay historical generated bytes onto current main.

Final product boundary — exactly 14 product/test paths

The valid rematerialized object is limited to:

  • apps/desktop/package.json
  • apps/desktop/scripts/before-pack-recovery.mjs
  • apps/desktop/scripts/before-pack.mjs
  • apps/desktop/scripts/before-pack.test.mjs
  • apps/desktop/scripts/desktop-builder-runtime.mjs
  • apps/desktop/scripts/desktop-pack-recovery-composition.test.mjs
  • apps/desktop/scripts/desktop-pack-transaction.mjs
  • apps/desktop/scripts/desktop-pack-transaction.test.mjs
  • apps/desktop/scripts/run-electron-builder.mjs
  • apps/desktop/scripts/stage-native-deps-recovery.mjs
  • apps/desktop/scripts/stage-native-deps-recovery.test.mjs
  • apps/desktop/vitest.config.ts
  • hermes_cli/main.py
  • tests/hermes_cli/test_desktop_pack_transaction_windows.py

Temporary .github publisher/materializer machinery is not product scope and must not survive into the final merge object.

Realization and readiness interlock — #96311 / #96280 / #96315

The source-announcement half of the earlier readiness class is now shipped. #96311 merged the canonical repair for #96282: serve announcements use the machine channel for READY and BACKEND_PORT_IN_USE, separate-stream behavior is covered, and a closed redirected stream cannot make the fallback kill a healthy serve. #96282 is therefore historical provenance, not an open implementation dependency.

#96280 remains a distinct Windows realization boundary. Electron can own a uv launcher process while the real serving interpreter is a grandchild. A packaged-Windows acceptance witness therefore cannot equate stdout ownership, process existence, or elapsed time with backend settlement. It must prove that the exact rebuilt generation is physically realized and observable through the authoritative readiness artifact/postcondition across launcher indirection.

#96315 independently demonstrates the other side of the shape on macOS: a backend may be alive while a generation-bound waiter still loses. That report is incident evidence; its proposed root cause is not promoted here to merged truth.

The package contract is therefore bytes + realized generation + observable settlement. Producing a coherent package tree is insufficient if the owner cannot prove which generation actually became usable.

Current post-release incident pressure

The v0.20.6 / v2026.8.27 release makes physical acceptance more important, not less. Current install/update reports include #96360 (Windows SCM false-blocker class), #96375 (stale renderer assets), #96409 (post-update Windows Desktop breakage), #96426 (updater exit-1), and #96442 (Linux sandbox after update). These are not asserted to share one root cause. They are evidence that packaging, update, realization, and rollback must be verified as a transaction rather than inferred from individual subprocess success.

Final acceptance transaction

  1. Re-read current-main overlaps across all fourteen product/test paths and compose the semantics once.
  2. Remove all temporary .github publisher/materializer paths from the final merge object.
  3. Put the reviewed hermes_cli/main.py transaction into the submitted tree only after reconciling it against then-current main.
  4. Preserve fix(serve): write machine sentinels to real fd 1 so the desktop handshake survives the stdout redirect (#96282) #96311’s shipped source-announcement behavior and Windows: desktop boot always fails with "Timed out waiting for Hermes backend port announcement (90000ms)" although backend is healthy — uv venv launcher grandchild stdout never reaches the spawn pipe; readyFile channel fixes it #96280’s launcher-independent realization requirement; do not make stdout inheritance a hidden Windows precondition.
  5. Prove the changed-file set is exactly the 14 paths above.
  6. Run fresh exact-final-head CI, Docker, and Nix.
  7. Run a physical packaged-Windows rebuild/recovery witness on that exact object, including realized launcher/interpreter topology and a successful generation-bound readiness postcondition.
  8. Verify lock-attested rollback, npm-selected runtime binding, Python launchability, generation fencing, native-dependency staging, and no destructive cleanup before rollback authority exists.
  9. Treat timeout, unknown generation, rollback ambiguity, or receipt mismatch as typed non-success. Do not promote indeterminate settlement to success.
  10. Re-read current review/topology state after the final head is materialized. Every final commit must own valid green evidence.

Until one current-main product-only object owns those receipts, this PR remains draft.

Provenance and interlocks

Disposition: #91079 remains the implementation owner for the still-missing Windows package transaction, but f84cecb… is not the final product object and has no transferable exact-head certification. Final truth requires one current-main 14-path product object plus physical packaged-Windows proof of package recovery, realized generation, readiness, and rollback settlement.

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) platform/windows Native Windows-specific behavior or breakage area/install-update Installer, updater, packaging, wheels, doctor P3 Low — cosmetic, nice to have 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 needs-decision Awaiting maintainer decision before any implementation labels Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Implementation receipt for the current exact head 8e641c78f1073321a79195b62c105982f6a01240:

Exact-head hosted receipts are all green:

This PR owns package-generation settlement and builder-runtime authority. #91063 remains the complementary corrupt-get-windows acquisition/staging repair; neither branch is being represented as the other's proof.

@andrexibiza
andrexibiza requested a review from a team August 21, 2026 00:15
@andrexibiza andrexibiza changed the title fix(desktop): make Windows package rebuild transactional fix(desktop): make Windows package rebuild transactional and self-healing Aug 21, 2026
@andrexibiza
andrexibiza force-pushed the fix/windows-desktop-pack-transaction branch from 10c3d19 to 914f3fe Compare August 21, 2026 00:17
@Adolanium

Copy link
Copy Markdown
Contributor

Don't merge this yet.

The restore-on-failure half is the right fix for the dead shortcut. If electron-builder dies, putting win-unpacked.bak back is what current main fails to do.

The success path is the problem. After a zero exit this deletes the backup as soon as the new Hermes.exe has an MZ header and a PE signature. That is weaker than the check we already have in Python (_ensure_desktop_exe_launchable). A truncated Electron extract and a wrong-arch exe both still look like a PE. Today the Python gate can roll those back. After this PR it can't, because the backup is already gone. Next retry then treats the broken exe as the "last good" build.

Leave the backup in place on success and let the existing gate decide. Or copy that full check (size, section table, arch) into JS before you throw the backup away. The current tests use a 256-byte stub and call that success, which is the bug.

Same recovery code still does npm install --package-lock=false --prefer-online and only checks that the version string is 9.3.0. You already called that a blocker on #91063: it skips the lockfile digest and the root tar override. That was not fixed before this PR swallowed #91063.

This also does not heal the corrupt node_modules tree, so every later Windows rebuild can pay for another isolated get-windows install. Fine as a bandage. It does not close #90829 on its own.

#44234 is still open and backs up in a different place. Pick one owner before both land.

The Node reexec for the fnm vs Hermes split looks right. barsh already showed that split on #90134. That part can stay.

Green CI here is not a Windows pack witness. The new suites are mocked unit tests on the generic JS lane. They never pack a real Hermes.exe, never hit a truncated Electron zip, and never run the Python integrity gate. Don't treat those runs as proof this survives the failure this PR claims to close.

@andrexibiza
andrexibiza force-pushed the fix/windows-desktop-pack-transaction branch from 93034ab to 13984a2 Compare August 21, 2026 03:04

Copy link
Copy Markdown
Contributor Author

Implemented the two load-bearing review repairs on the live branch rather than treating the earlier green matrix as acceptance.

Current exact head: aff2fa50fd933eff16d0be7442c1f497609eeacf.

  1. Rollback authority now survives builder success. baaeae568... strengthens the preliminary PE check through the COFF/section table and, more importantly, no longer lets the JS builder retire the last-good generation. A plausible replacement keeps win-unpacked.bak and its session marker so the existing Python _ensure_desktop_exe_launchable gate remains the final authority for host architecture / launchability and rollback retirement. Builder failure or structurally incomplete output still restores immediately.

  2. Isolated recovery no longer executes an unaudited registry graph. aff2fa50... makes recovery two-phase: materialize with lifecycle scripts disabled; derive the exact allowed dependency closure from the repository's committed package-lock.json; require every realized package's (version, resolved, integrity) tuple to occur in that closure; carry the repository overrides object into the recovery manifest and explicitly enforce the active tar override; only after that proof run the get-windows lifecycle. The graph is re-attested after lifecycle execution. Regression witnesses reject transitive-resolution drift before any lifecycle invocation and reject override drift.

I am also narrowing the topology claim: this bypasses a corrupt active node_modules tree safely for the package transaction; it does not heal the active tree itself, so #90829 should remain open as the broader persistent-install-health class rather than being auto-closed by this PR.

Fresh exact-head CI/Docker/Nix were triggered on aff2fa50...; they are currently pending/queued. No prior green is being inherited.

Copy link
Copy Markdown
Contributor Author

Fresh exact-head acceptance is now complete on aff2fa50fd933eff16d0be7442c1f497609eeacf:

The exact CI run includes green apps/desktop / check:test:desktop:all and check:test:desktop:platforms, plus a green OSV scan. The repository OS-specific tests job is skipped for this change, so I am not converting the green hosted matrix into a claim that the full corrupt-root → locked isolated realization → native binding → package → launchability path has been witnessed on a real win32-x64 host. That remains the final platform-specific evidence gap from the review.

Code-side blockers are closed on this head: rollback authority survives builder success through the canonical Python launchability gate, and isolated recovery cannot execute lifecycle code until the realized dependency graph is proven against committed lock/override authority. #90829 remains open as the broader active-install-health class.

Copy link
Copy Markdown
Contributor Author

Fresh exact-head settlement for aff2fa50fd933eff16d0be7442c1f497609eeacf:

These receipts are attached directly to the lock-closure / rollback-authority repair. No status is inherited from 8e641c78..., 1b89359a..., or any intermediate head.

The two load-bearing review blockers are closed in the exact object:

  1. JS cannot retire the last-good package generation after a merely plausible PE. Builder failure or structurally incomplete output restores immediately; apparent success retains the rollback tree and generation marker so the existing Python _ensure_desktop_exe_launchable gate remains the final authority for host architecture, launchability, commit, and retirement.
  2. Isolated get-windows recovery cannot execute lifecycle code until the realized package graph is proven against the committed package-lock.json closure and repository override policy. Every realized registry package must match an allowed (version, resolved, integrity) tuple; the active tar override is enforced; the graph is re-attested after lifecycle execution.

The evidence boundary remains explicit: this hosted matrix is exact-head repository evidence, not a real Windows packaged-app witness. Merge review should preserve that distinction, and the resulting merged object still needs current-main plus real-Windows/package-path verification before the incident class is described as operationally closed.

@andrexibiza andrexibiza reopened this Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Current-main delta — 2026-08-25 13:55 CDT

Upstream main has advanced to exact SHA 02c7ae956e42891d5e337a921b45de0a6067146d. The submitted #91079 object has not advanced: head remains ef6cef5d1733d185a972ccee051d5c6bcb35ea86.

Fresh topology against current main is now 17 commits ahead / 740 behind, merge base b40d85dc81097768bb4c913da78bee00dcd10fb3. The submitted diff is still 16 paths: the same 13 product/test paths plus the three temporary .github publisher/control-plane files. Required final-product path hermes_cli/main.py is still absent. The ef6cef5d… Actions-kick commit still has no exact-head PR/fork workflow receipt.

Current main itself is not a green acceptance floor: exact-main CI run 32875985041 is terminal failure at JS & TS checks / JS & TS checks (Run all workspace checks); the exact failing assertion/log text is not exposed through the available GitHub integration, so I am not attributing that failure to any open PR or known flake without evidence.

The acceptance invariant is unchanged: one semantic child of the exact landing main, exactly the declared 14 product/test paths, no temporary publisher machinery, then fresh exact-final-head CI/Docker/Nix plus a real packaged-Windows witness. Historical generated trees and prior greens remain provenance only.

@andrexibiza andrexibiza reopened this Aug 26, 2026
andrexibiza added a commit to andrexibiza/hermes-agent that referenced this pull request Aug 26, 2026
Install the actor-gated dispatcher that exposes and invokes the exact Windows-gated NousResearch#91079 finalizer. Temporary workflow; remove after publication read-back.
andrexibiza added a commit to andrexibiza/hermes-agent that referenced this pull request Aug 26, 2026
Install the temporary exact-current-main NousResearch#91079 composition and Windows witness lane. Remove after target publication and read-back.
andrexibiza added a commit to andrexibiza/hermes-agent that referenced this pull request Aug 26, 2026
Install the temporary server-originated scheduled execution lane for exact NousResearch#91079 current-main composition and real Windows verification. Remove after publication.
@andrexibiza
andrexibiza marked this pull request as draft August 27, 2026 03:01

Copy link
Copy Markdown
Contributor Author

Current publication correction after the live head moved again:

  • live head is now f84cecb3a630bd3dd07791d4e00ea94c06d5ba82 (the body still describes ef6cef5d…);
  • GitHub reports the current object not mergeable;
  • exact-head workflow lookup for f84cecb… returns zero runs;
  • the diff is now 18 paths, not the declared 14-path product boundary;
  • hermes_cli/main.py is still absent;
  • the carrier has expanded to five publication/control-plane paths: .github/pr91079-actions-kick, .github/pr91079-actions-kick-2, and three pr91079-* workflows.

I have moved this PR to draft so the repository does not mistake a newer publication carrier for the Windows package transaction itself.

The invariant remains unchanged: the landing object is one current-main semantic child containing only the declared 14 product/test paths, including the reconciled hermes_cli/main.py, with no materializer/finalizer machinery. That exact final head then needs CI/Docker/Nix plus a physical packaged-Windows rebuild/recovery witness proving lock-attested rollback, npm-selected runtime binding, Python launchability, and generation fencing. Historical materialization receipts do not transfer to f84cecb….

Copy link
Copy Markdown
Contributor Author

Implemented the publication/topology cleanup in place before adding this receipt. The PR body now names live head f84cecb3a630bd3dd07791d4e00ea94c06d5ba82, its draft/nonmergeable state, 22 commits / 18 paths, and the fact that exact-head workflow lookup returns zero registered runs.

The final merge contract is now explicit: one current-main 14-product-path Windows package transaction, no temporary .github publisher/materializer paths, reconciled hermes_cli/main.py, then fresh exact-final-head CI/Docker/Nix plus a physical packaged-Windows rebuild/recovery witness. Historical materializer artifacts remain provenance only.

andrexibiza commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Correction after direct live read-back: #91080 is not a package-transaction witness; it is the independent state.db WAL sidecar split-brain repro for #90950. I removed that false interlock from this PR body immediately.

The #91079 topology correction itself remains: #96311 shipped #96282’s source-announcement repair, while #96280 is the distinct packaged-Windows launcher/grandchild realization boundary. The final 14-path object must prove package bytes + realized backend generation + observable readiness + rollback settlement on the exact packaged-Windows artifact.

No standalone witness carrier is being treated as transferable product proof.

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/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have platform/windows Native Windows-specific behavior or breakage 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.

3 participants