Skip to content

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

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

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

Conversation

@danilofalcao

@danilofalcao danilofalcao commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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===

…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 covering
the copy behavior and guarding against reintroducing recursive cpSync.
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: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.

2 participants