Skip to content

[BUG] EPERM on Windows: fs.rename fails due to transient file locks (no retry) #227

@manzoorwanijk

Description

@manzoorwanijk

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

  1. On Windows (e.g., GitHub Actions windows-2025 runner) with Windows Defender real-time scanning enabled
  2. Create a project with install-strategy=linked in .npmrc
  3. Run npm install with a large dependency tree (many bin files with hashbangs)
  4. bin-links calls write-file-atomic for bin files -> fs.rename fails 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.3

Suggested 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 install on Windows with npm 10.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions