fix(pm): restore hardlink→copy fallback on clone failure#2801
Merged
fix(pm): restore hardlink→copy fallback on clone failure#2801
Conversation
Commit 13c698e (#2770) refactored `hardlink_clone::clone_dir`'s "try hardlink, fallback to copy" loop into an if/else that propagates the hardlink error. Cross-device installs (e.g. `ut i -g` where the cache lives on one filesystem and `/usr/local` on another) now fail with `Invalid cross-device link (os error 18)` instead of degrading to copy. Restore the fallback: on any `fs::hard_link` error (EXDEV, EPERM, EMLINK, …) emit one `tracing::warn!` and switch the rest of this clone to `copy_file_sync`. The old fallback was silent; the warn log makes the degraded-path condition observable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates the file cloning logic in crates/pm/src/util/cloner.rs to implement a fallback mechanism from hardlinking to copying. When a hardlink operation fails—for instance, due to cross-device link errors—the system now logs a warning and automatically switches to copying for the current and all subsequent files in the batch. I have no feedback to provide.
When the initial `install_pkgs` hits the 60s timeout mid-unpack (seen on
a libc6 upgrade), dpkg is left in a half-configured state. The retry on
the fallback mirror then fails immediately with:
E: dpkg was interrupted, you must manually run 'dpkg --configure -a'
to correct the problem.
Finalise any pending unpack before the retry so the fallback mirror
attempt actually has a chance to succeed. Keep `|| true` — if there is
nothing to configure, dpkg still exits 0, but we don't want a rare
unrelated failure to mask the real `install_pkgs` retry error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…thers EXDEV is a property of the src/dst pair — once one file hits it, every remaining file on the same clone will fail the same way, so it's safe (and much cheaper) to latch `force_copy` and skip hardlink attempts for the rest of this clone. Other hardlink errors (EMLINK when a single inode's link count is exhausted, EPERM on a specific file) are per-file. Latching the whole clone to copy on one of those would downgrade every subsequent file unnecessarily. Handle them by copying just the failing file and continuing to try hardlink on the next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two unit tests for `hardlink_clone::clone_dir`: - `test_clone_dir_per_file_fallback_does_not_latch`: pre-populates the destination with a conflicting file so `fs::hard_link` fails with `AlreadyExists` (a non-EXDEV ErrorKind). Verifies (a) the failing file is recovered by the copy fallback, and (b) a subsequent, non-conflicting file is still hardlinked — the per-file fallback must not latch the whole clone to copy. - `test_clone_dir_install_script_forces_copy`: writes `_hasInstallScript` in the cache layout so `has_install_script_sync` returns true, forcing `force_copy` upfront. Verifies every file is copied (distinct inode from src), which is the safety property we need so install-script mutations can't leak back into the shared cache via hardlinks. Both tests use `st_ino` to distinguish hardlink from copy, so they require Unix and are gated `#[cfg(unix)]` inside the existing `hardlink_clone_tests` module (itself `#[cfg(not(target_os = "macos"))]`). The EXDEV-latch branch of the fallback requires actual cross-device mounts to exercise and is left to the manual repro documented in the PR description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fireairforce
approved these changes
Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hardlink_clone::clone_dirused to tryfs::hard_linkfirst and fall back tocopy_file_syncon any error. Commit 13c698e (feat(pm): support multi-workspace selection for run command #2770, the multi-workspace refactor) rewrote the loop into anif/elsethat propagates the hardlink error, so cross-device installs now hard-fail:ut i -gon any machine where$HOME/.cache/nmand/usr/local/lib/node_modulesare on different filesystems (e.g. Jenkins pods, some container images).fs::hard_linkfailure (EXDEV, EPERM, EMLINK, …), emit onetracing::warn!for the pair that failed and flipforce_copy = trueso the rest of this clone goes throughcopy_file_sync. Once one file in asrc/dstpair fails the others will fail the same way, so retrying hardlink is wasted work.use_copy→force_copy— the variable now means “we were forced off the hardlink path,” which is a more accurate name once the flag can flip at runtime.warn!makes the degraded-path condition visible in logs, which is how we would have caught this regression sooner.Out of scope: the macOS
clonefilepath also has no cross-device fallback. The user's report is Linux, and the syscall/failure modes differ — handle as a follow-up.Test plan
cargo fmt -p utoo-pmcargo clippy -p utoo-pm --all-targets -- -D warnings --no-depscargo test -p utoo-pm util::cloner(16 passed; the fallback branch is unreachable on a single-fs test host, which is expected)ut i -g node-gyp. Expect success with onehardlink failed … falling back to copy for remaining fileswarning per package.hardlink_clonemodule, which is cfg-gated off on the macOS dev host).🤖 Generated with Claude Code