Skip to content

fix(arborist): retry bin-links on Windows EPERM#9028

Open
manzoorwanijk wants to merge 1 commit intonpm:latestfrom
manzoorwanijk:fix/linked-strategy-windows-eperm
Open

fix(arborist): retry bin-links on Windows EPERM#9028
manzoorwanijk wants to merge 1 commit intonpm:latestfrom
manzoorwanijk:fix/linked-strategy-windows-eperm

Conversation

@manzoorwanijk
Copy link
Contributor

@manzoorwanijk manzoorwanijk commented Feb 25, 2026

In continuation of our exploration of using install-strategy=linked in the Gutenberg monorepo, which powers the WordPress Block Editor, this is a follow up of the fixes from #8996.

On Windows, npm install with install-strategy=linked fails with EPERM: operation not permitted during the bin-linking phase. This is hitting us on Windows CI for the Gutenberg monorepo (~200 workspace packages).

Summary

During rebuild, bin-links/fix-bin.js rewrites hashbang lines in bin files using write-file-atomic, which does a temp-file write followed by fs.rename(). On Windows, antivirus (Windows Defender) and the search indexer can transiently lock files that were just written, causing the rename to fail with EPERM.

The linked strategy amplifies this because it writes all packages into .store/ in parallel, increasing the window for antivirus lock conflicts compared to the hoisted layout.

Root cause

write-file-atomic uses bare require('fs') and has no retry logic for fs.rename. By contrast, graceful-fs already patches fs.rename on Windows with exponential backoff for EACCES/EPERM/EBUSY (up to 60s). Since write-file-atomic doesn't use graceful-fs, the rename fails immediately on the first lock conflict.

The longer-term fix is for write-file-atomic to use graceful-fs (tracked in npm/write-file-atomic#227, with a PR ready - npm/write-file-atomic#228), but in the meantime this PR adds retry logic at the arborist level.

Changes

  • Wrapped the binLinks() call in rebuild.js #createBinLinks with a new #binLinksWithRetry method that retries up to 5 times with 500ms–2.5s backoff on Windows when the error code is EPERM, EACCES, or EBUSY.
  • The retry only activates on process.platform === 'win32' — no behavior change on macOS/Linux.

Testing

We tested this approach in our fork and it resolves the issue on Windows CI for the Gutenberg monorepo.

References

Fixes #9021

@manzoorwanijk manzoorwanijk requested a review from a team as a code owner February 25, 2026 05:06
@manzoorwanijk manzoorwanijk force-pushed the fix/linked-strategy-windows-eperm branch 2 times, most recently from 9111028 to bff4ae0 Compare February 25, 2026 05:18
…y install

On Windows, antivirus and search indexer can transiently lock files,
causing write-file-atomic's fs.rename to fail with EPERM during the
bin-linking phase. The linked strategy amplifies this by writing many
store entries in parallel.

Add retry with backoff (up to 5 attempts) for EPERM/EACCES/EBUSY
errors in #createBinLinks, Windows only.
@manzoorwanijk manzoorwanijk force-pushed the fix/linked-strategy-windows-eperm branch from bff4ae0 to 0d8fa42 Compare March 3, 2026 07:14
Comment on lines +18 to +20
// On Windows, antivirus/indexer can transiently lock files, causing
// EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by
// bin-links/fix-bin.js). Retry with backoff for up to ~7.5 seconds.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// On Windows, antivirus/indexer can transiently lock files, causing
// EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by
// bin-links/fix-bin.js). Retry with backoff for up to ~7.5 seconds.
// On Windows, antivirus/indexer can transiently lock files, causing EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by bin-links/fix-bin.js).
// Retry with backoff for up to ~7.5 seconds.

As part of our efforts to increase accessibility we try to keep comments all on one line, breaking only for sentences.

// EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by
// bin-links/fix-bin.js). Retry with backoff for up to ~7.5 seconds.
/* istanbul ignore next */
const retryBinLinks = async (binLinks, node, opts, retries = 4) => {
Copy link
Member

Choose a reason for hiding this comment

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

Elsewhere we have delegated retries to a library. There is a LOT that goes into retrying and it's not something we want to hand roll.

return promiseRetry(async (retry) => {

Comment on lines +33 to +34
} catch (err) {
if (retries > 0 &&
Copy link
Member

Choose a reason for hiding this comment

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

This is a good example of why a retry library is the right option. We can wrap this whole thing in the retry library and then make the decision on whether or not to retry it at all (by checking codes) in our callback.

@wraithgar
Copy link
Member

We may want to dig into the write-file-atomic PR first to see why this isn't already happening.

@wraithgar wraithgar self-assigned this Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] EPERM on Windows with install-strategy=linked: fs.rename fails in write-file-atomic

2 participants