pnpm-fs's Windows retry policy treats every PermissionDenied error as a transient file lock:
https://github.com/pnpm/pnpm/blob/main/pnpm/crates/fs/src/retry.rs
pub(crate) fn is_transient_file_lock_error(error: &io::Error) -> bool {
cfg!(windows)
&& (matches!(error.kind(), io::ErrorKind::PermissionDenied | io::ErrorKind::ResourceBusy)
|| matches!(error.raw_os_error(), Some(ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION)))
}
ERROR_ACCESS_DENIED (5) maps to PermissionDenied, and a scanner holding a handle is only one of the things that produces it. A read-only file, a restrictive ACL, or a directory occupying the destination path produces the same error and will never clear. Those failures consume the full RETRY_BUDGET of one minute before the operation returns the error it already had on the first attempt.
A native Windows probe with a directory occupying the destination returned error 5 after 603 attempts and 60.0004645 seconds. The directory and its child were preserved, so the outcome is correct; only the latency is wrong.
Every caller of the policy is affected: rename_with_retry, remove_dir_all_with_retry, and remove_file_with_retry. #14573 widened the exposure by routing command-shim removal and the shim replacement rename through it, and it is why bin_cleanup_and_replacement_preserve_deletion_errors is ignored on Windows.
What to decide:
- Whether a sharing or lock violation (32, 33) plus
ResourceBusy is the right transient set, and what to do about error 5, which is genuinely ambiguous. A rename of a directory blocked by an open handle below it reports 5 and is transient, which is why it is in the set today.
- Whether the answer differs per operation. Removal and rename may warrant different classifiers than recursive directory removal.
- Whether a permanent failure should fail fast or keep a much shorter budget.
Whatever lands needs native Windows tests that a read-only file and a restrictive ACL fail fast, alongside the existing transient-lock recovery tests, and bin_cleanup_and_replacement_preserve_deletion_errors should then run on Windows instead of being ignored.
Written by an agent (Claude Code, claude-opus-5).
pnpm-fs's Windows retry policy treats everyPermissionDeniederror as a transient file lock:https://github.com/pnpm/pnpm/blob/main/pnpm/crates/fs/src/retry.rs
ERROR_ACCESS_DENIED(5) maps toPermissionDenied, and a scanner holding a handle is only one of the things that produces it. A read-only file, a restrictive ACL, or a directory occupying the destination path produces the same error and will never clear. Those failures consume the fullRETRY_BUDGETof one minute before the operation returns the error it already had on the first attempt.A native Windows probe with a directory occupying the destination returned error 5 after 603 attempts and 60.0004645 seconds. The directory and its child were preserved, so the outcome is correct; only the latency is wrong.
Every caller of the policy is affected:
rename_with_retry,remove_dir_all_with_retry, andremove_file_with_retry. #14573 widened the exposure by routing command-shim removal and the shim replacement rename through it, and it is whybin_cleanup_and_replacement_preserve_deletion_errorsis ignored on Windows.What to decide:
ResourceBusyis the right transient set, and what to do about error 5, which is genuinely ambiguous. A rename of a directory blocked by an open handle below it reports 5 and is transient, which is why it is in the set today.Whatever lands needs native Windows tests that a read-only file and a restrictive ACL fail fast, alongside the existing transient-lock recovery tests, and
bin_cleanup_and_replacement_preserve_deletion_errorsshould then run on Windows instead of being ignored.Written by an agent (Claude Code, claude-opus-5).