Skip to content

fix: fall back to copy when hard links fail in pnpm linker#7067

Open
VforVitality wants to merge 1 commit intoyarnpkg:masterfrom
VforVitality:fix/5326-pnpm-linker-hardlink-fallback
Open

fix: fall back to copy when hard links fail in pnpm linker#7067
VforVitality wants to merge 1 commit intoyarnpkg:masterfrom
VforVitality:fix/5326-pnpm-linker-hardlink-fallback

Conversation

@VforVitality
Copy link
Copy Markdown

Summary

When using
odeLinker: pnpm, the content-addressable index creates hard links from the global index to
ode_modules/.store/. This fails in several common scenarios:

Error code Cause
EXDEV Index and project on different volumes (Docker, CI, Windows multi-drive)
EMLINK Inode hard link limit exceeded (Linux/macOS)
UNKNOWN (link syscall) NTFS hard link limit of 1024 per file (Windows) — libuv doesn't map \ERROR_TOO_MANY_LINKS\ to EMLINK
EPERM (link syscall) Insufficient privileges for hard links (Windows without Developer Mode)

This PR adds graceful degradation: when \linkPromise\ fails with any of these errors, the file content is copied from the index instead. This matches the behavior of the real pnpm CLI, which also falls back to copying.

Changes

  • **\packages/yarnpkg-fslib/sources/algorithms/copyPromise.ts**: Added \shouldFallbackToCopy()\ helper and wrapped the \linkPromise\ call in \copyFileViaIndex()\ with try/catch + copy fallback
  • **\packages/yarnpkg-fslib/tests/copyPromise.test.ts**: Added comprehensive unit tests covering EXDEV, EMLINK, UNKNOWN+link, EPERM+link fallback, plus negative tests (UNKNOWN without link syscall, EACCES propagation)

Testing

Unit tests

All 8 test suites pass (104 passed, 5 platform-skipped):
\
yarn test:unit yarnpkg-fslib # ✅ 8 passed, 0 failed
\\

Real-world validation

Tested against a large monorepo (~40 workspaces) with @mui/icons-material\ (~17,000 .d.ts\ files with identical content hashing to the same index entry):

  • Before fix: \UNKNOWN: unknown error, link\ (errno -4094) — Yarn crashes at ~1024 hard links
  • After fix: Install completes successfully. Files at the NTFS limit are hard-linked (1023 links), overflow files are transparently copied

Fixes #5326

if (shouldFallbackToCopy(err)) {
const content = await destinationFs.readFilePromise(indexPath);
await destinationFs.writeFilePromise(destination, content);
if (sourceMode !== defaultMode)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing curly bracket

Suggested change
if (sourceMode !== defaultMode)
if (sourceMode !== defaultMode) {

Comment on lines +1 to +2
import {NodeFS} from '../sources/NodeFS';
import {xfs, PortablePath, ppath, Filename, npath} from '../sources';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Linter fix

Suggested change
import {NodeFS} from '../sources/NodeFS';
import {xfs, PortablePath, ppath, Filename, npath} from '../sources';
import {NodeFS} from '../sources/NodeFS';
import {xfs, ppath, Filename} from '../sources';

@tobiasweibel
Copy link
Copy Markdown

@VforVitality it would be nice to get this done. It would probably fix an issue I see when hard linking files from outside of a docker into the image.

@VforVitality VforVitality force-pushed the fix/5326-pnpm-linker-hardlink-fallback branch from bd34268 to 98ae0a7 Compare March 29, 2026 19:29
@VforVitality
Copy link
Copy Markdown
Author

Hi @tobiasweibel
Thank you for help and review, I'm sorry for being so slow :D

The thing is that we started using a 'same-way-patched' yarn (4.7.0 in our case) and even though it works with the fallback, we experienced some performance degradation on ntfs.

So I went deeper with my research and turned out that try to link, then catch is slow. So I tried to implement threshold, and not even try to link beyond that threshold. Seems to be working for now (plan to test it this week).

I'm only not sure if this improvement should be a part of this PR? Anyone, any opinion? Shall it just fix the issue or also to try to improve performance a bit?

When using nodeLinker: pnpm, the content-addressable index creates hard
links from the index to node_modules/.store/. This fails in several
scenarios:

- EXDEV: index and project on different volumes (common in Docker, CI,
  and Windows multi-drive setups)
- EMLINK: inode hard link limit exceeded (Linux/macOS)
- UNKNOWN (link syscall): NTFS hard link limit of 1024 per file
  (Windows); libuv does not map ERROR_TOO_MANY_LINKS to EMLINK
- EPERM (link syscall): insufficient privileges for hard links

This commit adds a shouldFallbackToCopy() helper that detects these
errors, and wraps the linkPromise call in copyFileViaIndex() with a
try/catch that gracefully degrades to copying the file content. This
matches the behavior of the real pnpm CLI.

Fixes yarnpkg#5326
@VforVitality VforVitality force-pushed the fix/5326-pnpm-linker-hardlink-fallback branch from 98ae0a7 to e311aff Compare March 29, 2026 19:44
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?]: pnpm linker — cross-device link not permitted

3 participants