-
Notifications
You must be signed in to change notification settings - Fork 49
Description
Is there an existing issue for this?
- I have searched the existing issues
This is a follow-up to #28 (open since 2017) with fresh details. The problem has become more prominent with npm's install-strategy=linked, which we've also reported as npm/cli#9021.
Current Behavior
On Windows, writeFile() fails with EPERM: operation not permitted when fs.rename(tmpfile, truename) is called and another process (Windows Defender, Windows Search indexer, or a concurrent Node.js worker) holds a transient lock on the target file.
This surfaces in npm's install-strategy=linked where bin-links/fix-bin.js calls write-file-atomic to rewrite hashbang lines in bin files. The linked strategy creates many files in .store/ in parallel, which triggers antivirus scanning. The fs.rename call fails immediately with no retry:
EPERM: operation not permitted, rename
'...\jscodeshift\bin\jscodeshift.js.2477615846'
-> '...\jscodeshift\bin\jscodeshift.js'
The root cause is that write-file-atomic uses bare require('fs') (line 7). graceful-fs already patches fs.rename on Windows with exponential backoff (up to 60s) for EACCES/EPERM/EBUSY errors (polyfills.js lines 96-120), but write-file-atomic doesn't use it and fails immediately.
This has always been a latent issue (#28), but npm's linked strategy makes it far more likely to trigger. The linked layout concentrates all file writes into a single .store/ directory with heavy parallelism, increasing the window for antivirus lock conflicts compared to the hoisted layout which spreads writes across node_modules/.
Expected Behavior
write-file-atomic should retry fs.rename on Windows when it fails with EACCES, EPERM, or EBUSY, since these are transient errors caused by file locking.
Steps To Reproduce
- On Windows (e.g., GitHub Actions
windows-2025runner) with Windows Defender real-time scanning enabled - Create a project with
install-strategy=linkedin.npmrc - Run
npm installwith a large dependency tree (many bin files with hashbangs) bin-linkscallswrite-file-atomicfor bin files ->fs.renamefails with EPERM
The issue is more likely to occur with large projects (many parallel file writes) that increase the window for antivirus lock conflicts. We hit this consistently with the Gutenberg monorepo (~200 workspace packages).
Failed CI run: https://github.com/WordPress/gutenberg/actions/runs/22299881844/job/64504879968?pr=75814
Standalone (PowerShell)
mkdir test-linked
cd test-linked
npm init -y
# Enable linked strategy
"install-strategy=linked" | Out-File -Encoding utf8 .npmrc
# Install packages with many transitive deps and bin files with hashbangs
npm install jscodeshift@0.14.0 @react-native/debugger-frontend@0.73.3Suggested Fix
The most straightforward fix would be to switch back to graceful-fs:
- const fs = require('fs')
+ const fs = require('graceful-fs')graceful-fs was originally a dependency of write-file-atomic but was removed in v3.0.0 to break a circular dependency that interfered with graceful-fs's own test suite (isaacs/node-graceful-fs#163). That testing concern is valid, but it left write-file-atomic without any Windows rename retry, which is now causing real failures. It's also worth noting that tap no longer depends on write-file-atomic, so the original circular dependency concern no longer applies.
If re-adding graceful-fs as a dependency isn't desirable, an alternative would be to add targeted retry logic for fs.rename on Windows directly in write-file-atomic -- retry on EACCES/EPERM/EBUSY with backoff, similar to what graceful-fs does.
Environment
- npm: 10.9.x / 11.x
- Node: 20.x, 22.x, 24.x
- OS: Windows Server 2025 (GitHub Actions
windows-2025) - platform: Windows (any version with real-time antivirus)
Related
- #28 -- original report of this issue from 2017, via Jest parallel workers (facebook/jest#4444)
- npm/cli#9021 -- the npm CLI issue we filed for the linked strategy EPERM failures
- npm/cli#8072 -- EPERM during
npm installon Windows with npm 10.x