install: skip .bin link recreate when the existing symlink already matches

Closed
RO
1mo ago
+67-26

Fixes #14736.

Repro

With a project whose node_modules (including node_modules/.bin/tsc) is already fully installed and then mounted read-only (the docker-compose ./:/home/bun/app:ro pattern from the issue, or chmod -R a-w as an unprivileged user):

$ bun install && bun index.ts
error: Failed to link typescript: EEXIST

Cause

On every install, including a no-op one, the hoisted/isolated bin linker re-links each package bin. create_symlink in src/install/bin.rs handles the common case where node_modules/.bin/<name> already exists by unconditionally unlink + re-symlink. On a read-only tree the unlink fails (error discarded) and the retry symlink returns the original EEXIST, which surfaces as Failed to link <pkg>: EEXIST and fails the install even though nothing needed to change.

Fix

When the first symlink returns EEXIST, readlink the existing destination and compare against the relative target we were about to write. If they already match, return success and skip the delete+recreate entirely. A stale link (or a non-symlink, where readlink fails) still falls through to the existing delete+retry path, so version upgrades and the "existing non-symlink" case keep replacing the destination.

Verification

New test binaries > reinstall keeps an already-correct .bin link untouched on a read-only tree in test/cli/install/bun-install-registry.test.ts:

  • installs what-bin@1.0.0, makes node_modules/.bin mode 0555, reinstalls
  • asserts no Failed to link / error: in stderr and exit 0
  • asserts the symlink's ino/ctimeNs are unchanged, so the skip is observable even on hosts where root bypasses the chmod

Before: ctime/inode change on every reinstall; on unprivileged hosts the reinstall errors with Failed to link what-bin: EEXIST.
After: second install is a true no-op on the .bin entry.


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-install-registry.test.ts