Skip to content

fix(desktop): avoid recursive cpSync in stage-native-deps (Windows build crash) - #61832

Closed
danilofalcao wants to merge 1 commit into
NousResearch:mainfrom
danilofalcao:fix/desktop-cpsync-windows-crash
Closed

danilofalcao wants to merge 1 commit into
NousResearch:mainfrom
danilofalcao:fix/desktop-cpsync-windows-crash

Conversation

@danilofalcao

Copy link
Copy Markdown

What does this PR do?

Fixes a hard crash that makes the desktop app impossible to build/package on Windows.

npm run pack (invoked by hermes desktop) dies silently at the stage-native-deps step with exit code 3221226505 (0xC0000409 / STATUS_STACK_BUFFER_OVERRUN):

bundled ...\dist\electron-preload.js
npm error Lifecycle script `build` failed with error:
npm error code 3221226505

Root cause: Node 22.x's native recursive cpSync (cpSyncCopyDir) is unreliable on Windows. cpSync(src, dest, { recursive: true }) throws EIO: "Access is denied." on the \\?\-prefixed destination — reproducible even for a plain directory of .js files — and hard-crashes the whole process (0xC0000409) when copying node-pty's conpty payload (OpenConsole.exe + conpty.dll). Per-file, non-recursive cpSync works every time.

Because the fault is native (not a JS exception), there's no stack trace and it can't be caught/retried — the process just vanishes. The desktop launcher then misreports this as a blocked Electron download and retries via a mirror, but the build never actually reaches the electron-builder step, so the retry can't help.

The fix routes the two recursive-cpSync directory copies in stage-native-deps.mjs through a small manual walk (copyDirRecursive) that copies files individually — the exact pattern the neighbouring copyGlobByExt already uses reliably. No behavior change on macOS/Linux (they were fine; this just uses a codepath that also works on Windows).

Related Issue

No linked issue — happy to open one if you'd prefer to track it. Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • apps/desktop/scripts/stage-native-deps.mjs
    • Added copyDirRecursive(srcDir, destDir) — walks a directory with readdirSync and copies each file with a non-recursive cpSync (recursing into subdirs itself), with a comment explaining why recursive cpSync is avoided.
    • Replaced the two cpSync(dir, { recursive: true }) calls (copyBuildRelease for build/Release/* subdirs, and the prebuilds/<target>/conpty copy) with copyDirRecursive.
  • apps/desktop/scripts/stage-native-deps.test.mjs (new)
    • Verifies copyDirRecursive reproduces a nested tree (incl. a conpty/ subdir with binary blobs) with byte-exact copies.
    • Verifies it creates the destination even for an empty source.
    • Regression guard asserting stage-native-deps.mjs never calls cpSync with { recursive: true } (the crashing API).

How to Test

Reproduction (Windows 11, Node 22.x, from apps/desktop):

  1. On main: npm run build → crashes at stage-native-deps with npm error code 3221226505, no stack trace.
  2. With this PR: npm run build → exits 0; npm run pack → exits 0 and produces release/win-unpacked/Hermes.exe with the node-pty conpty binaries staged under resources/app.asar.unpacked/dist/node_modules/node-pty/.

Unit tests:

cd apps/desktop && node --test scripts/stage-native-deps.test.mjs
# tests 3  pass 3  fail 0

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — N/A, this is a desktop build-script (Node) change; ran node --test scripts/stage-native-deps.test.mjs (3/3 pass) instead
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11 (WSL2), Node 22.23.x — full npm run pack now succeeds

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — code comment added explaining the Windows cpSync pitfall; no user-facing docs affected
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — macOS/Linux behavior unchanged (they already worked); fix only swaps to a codepath that additionally works on Windows
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

Before (on main):

  dist\electron-preload.js  15.5kb
bundled ...\dist\electron-preload.js
npm error Lifecycle script `build` failed with error:
npm error code 3221226505

After (this PR):

[stage-native-deps] staged node-pty (win32-x64) -> ...\apps\desktop\dist\node_modules\node-pty
> hermes@0.17.0 postbuild
✓ assert-dist-built: dist/index.html + assets present
  • packaging       platform=win32 arch=x64 electron=40.10.2 appOutDir=release\win-unpacked
===PACK_ERRORLEVEL=0===

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 10, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating both native staging paths. Current main still has the two recursive directory copies at apps/desktop/scripts/stage-native-deps.mjs:71 and :109; this PR replaces both, and no additional recursive cpSync call was found in the desktop staging script.

Problems

  • apps/desktop/scripts/stage-native-deps.test.mjs:65 only recognizes recursive when it occurs on the same line as cpSync. A multiline cpSync(..., { recursive: true }) would bypass the intended regression guard.

Suggested changes

  • Make that source-level assertion newline-tolerant so it protects the stated invariant regardless of formatting.

Automated hermes-sweeper review.

Comment thread apps/desktop/scripts/stage-native-deps.test.mjs Outdated
@danilofalcao
danilofalcao force-pushed the fix/desktop-cpsync-windows-crash branch from 8fa4195 to 0bb76a8 Compare July 10, 2026 17:17
@danilofalcao
danilofalcao requested a review from teknium1 July 11, 2026 12:40
@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 11, 2026
@danilofalcao
danilofalcao force-pushed the fix/desktop-cpsync-windows-crash branch from 0bb76a8 to 3e65f3d Compare July 13, 2026 22:30
…ild crash)

Node 22.x's native recursive cpSync (cpSyncCopyDir) is unreliable on
Windows. Copying a directory tree throws `EIO: "Access is denied."` on the
`\\?\`-prefixed destination — even for a plain directory of .js files — and
hard-crashes the whole process (exit 3221226505 / 0xC0000409 /
STATUS_STACK_BUFFER_OVERRUN) while copying node-pty's conpty payload
(OpenConsole.exe + conpty.dll). Per-file, non-recursive cpSync works fine.

The crash is silent (no stack trace, since it's a native fault, not a JS
throw) and killed `npm run pack` at the stage-native-deps step. The desktop
launcher misreports this as a blocked Electron download and retries via a
mirror, but the build never actually reaches the electron-builder step.

Route the two `cpSync(dir, { recursive: true })` calls through a manual
walk (copyDirRecursive) that copies each file individually — the same
pattern copyGlobByExt already uses reliably. Add regression tests for the
copy behavior and a newline-tolerant source guard that parses each cpSync
call's arguments so a multi-line recursive call can't slip back in.
@danilofalcao

Copy link
Copy Markdown
Author

@teknium1 ping

teknium1 added a commit that referenced this pull request Sep 17, 2026
…k .bak wipe

The salvaged commits drop the cpSync/rmSync imports, but stageGetWindowsInto
landed on main after the PR was opened and still called both, so the module
threw ReferenceError on every platform (7 vitest failures on the cherry-picked
head). Route its six sites through copyFileSync/removeDirSync so get-windows
staging survives the same non-ASCII profile paths as node-pty.

preserveRollbackBackup had the same rmSync no-op as cleanStaleAppOutDir: a
surviving .bak makes renameSync fail, so the previous working build gets wiped
instead of kept as rollback material. Same existsSync -> removeDirSync fallback.

Earlier fixes for the same crash: #60480 (@liuhao1024), #61832 (@danilofalcao),
#76211, #103458, #109273, #111590.
Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: danilofalcao <danilofalcao@users.noreply.github.com>
@teknium1

Copy link
Copy Markdown
Collaborator

Fixed on main by #113919 (4146fcbd7a7), which lands @kosazoltan's #66687 — the same approach as here (libuv-backed copyFileSync walks instead of fs.cpSync/fs.rmSync in apps/desktop/scripts/stage-native-deps.mjs), widened to rmSync, before-pack.mjs and the get-windows staging. Thanks for the report and fix; closing as superseded.

@teknium1 teknium1 closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) 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-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