cpSync recreates a symlink when patching the bundled Undici proxy — every command fails with EPERM on Windows
Description
On a normal (non-elevated) Windows machine, every Feynman command fails — including feynman --version — once any optional package is installed.
Environment
- OS: Windows 11 Pro (build 26200), x64, non-elevated, Developer Mode off
- Feynman: current main (dfdcb7c)
- Node: 24.x
Steps to reproduce
- Install any optional package (e.g.
memory). - Run
feynman --version.
Actual behavior
Fails with EPERM during startup. patchPiUndiciProxyTree throws the production error including the .feynman-proxy-<PID> suffix.
Root cause
scripts/lib/pi-undici-proxy-patch.mjs:173. safePackagePath resolves to node_modules/undici, a link created by linkBundledPackage. cpSync defaults to dereference: false, so the patch recreates a symlink. Creating a dir-type symlink requires SeCreateSymbolicLinkPrivilege, which a non-elevated user does not hold on Windows — the operation fails with EPERM.
The repo already handles this elsewhere: linkBundledPackage (scripts/patch-embedded-pi.mjs:515) uses junction on Windows for exactly this reason, and scripts/verify-stale-pi-upgrade.mjs:460 already passes dereference: true.
Expected behavior
Real files are copied (undici 8.10.2), no symlink is created, and feynman --version succeeds.
Proposed fix (PR attached)
Pass dereference: true to the cpSync call. The regression test asserts isSymbolicLink() === false, which fails on Linux too, so the existing ubuntu CI run catches regressions — a Windows-only test would be redundant.
Impact
Feynman is unusable on Windows once any optional package is installed.
Note on CI coverage
npm test runs only on ubuntu-latest (.github/workflows/e2e.yml:298, publish.yml:236); the Windows CI jobs exercise only the installer. GitHub's Windows runners are administrator-equivalent and hold SeCreateSymbolicLinkPrivilege, so symlinkSync(..., "dir") succeeds there and this bug is invisible to CI.
Verification
npm run typecheck and npm run build pass; the touched test file passes in full. The full suite on Windows has 109 pre-existing failures at clean main; the failing set on this branch is identical (2 additional failures were confirmed flaky).
Source: companion-inc/feynman