Repository navigation
Fix npm bin shebang on unlinked bins (#1804) - #1805
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Since #1792, apply and rollback treated every file a package lists as a bin as one npm had rewritten to an LF shebang at install. npm only does that for bins it links. With bin-links=false (--no-bin-links), or for a directories.bin file of a package installed from a tarball or folder, the installed file keeps its CRLF shebang, so apply wrote bytes that matched neither the patch nor a fresh install, and rollback no longer restored the installed file byte for byte. A listed bin now counts as npm's form only when npm linked it: a .bin/<name> entry in a node_modules the package sits under (a symlink must point at this file), a pnpm dependent's .bin entry, or a global prefix bin or .cmd shim. Bins npm did link, including ones an older release left with a CRLF shebang (#1791), are still repaired. Fixes #1804 Assisted-by: Claude Code:claude-opus-5-5
npm links a package's bins into the .bin folder of the node_modules the package sits in, so a nested copy is no longer taken as linked because the top-level copy of the same package has a .bin entry. Packages in a virtual store (pnpm, npm's linked strategy, vlt, Bun) get their bins linked into each dependent instead, so there the bin listing alone still counts, as it did before. Also check the on-disk shebang before the link, which is cheaper, and read a bin key with a trailing slash the way npm does. Refs #1804 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
1 similar comment
|
BugBot review Generated by Claude Code |
A regular file that merely had a bin's name, such as a project's own <name>.cmd or a lib/../bin script, counted as npm's link, so a bin npm never linked could still be rewritten with an LF shebang. A shim now has to name this file's path below node_modules, as npm's and pnpm's shims do; a symlink still has to resolve to it. Copies in a store npm doesn't lay out (outside any node_modules, as in Yarn's pnpmStoreFolder, or in a name@version entry of a relocated pnpm virtualStoreDir) keep taking the bin listing as the link, as before. Refs #1804 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review at d2387bf.
Generated by Claude Code |
Final review briefWhat it does. Since #1792, Risk: medium. It changes which bytes Look here
Verified
Changes I made: none. Open questions (non-blocking)
Auto-merge (squash) is armed: approving sends this straight to the merge queue. Generated by Claude Code |
A shim for another package whose folder name ends the same way, such as a-crlfbin/cli.js, or a sibling file like cli.json, counted as running this package's bin. The shim now has to name the path as whole segments. Refs #1804 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Addressed the final review's open question in 5789c73: Generated by Claude Code |
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5789c73. Configure here.
|
Burn-down agent: Ready for review confirmed at
Generated by Claude Code |
Final review brief (updated for
|
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1804
Summary
Since #1792,
applyandrollbacktreated every file a package lists as a bin as one npm had rewritten to an LF shebang at install. npm only does that (fixBin) for bins it links. Withbin-links=false(--no-bin-links), or for adirectories.binfile of a package installed from a tarball/folder (npm expands that field only in registry packuments), the installed bin keeps its CRLF shebang. Soapplywrote bytes matching neither the patch'safterHashnor a fresh install, androllbackwas no longer byte-exact.Root cause
npm_bin::form_for_writeandnpm_bin::needs_fixtook thepackage.jsonlisting (is_listed_bin) as proof that npm rewrote the shebang, ignoring whether npm linked the bin at all.Fix
is_listed_binis nowis_linked_bin: a listed bin counts as npm's form only when npm linked it:.bin/<name>entry in thenode_modulesthe package sits in (where npm links it; a symlink must resolve to this file, and a cmd/shell shim must name this file's path belownode_modules), under the names npm links it as (binkeys, unscoped package name, file names for arrays anddirectories.bin);bin/<name>(Unix) or<name>.cmd(Windows);.pnpmor a relocatedvirtualStoreDir, npm linked.store, vlt, Bun, or a copy outside anynode_modulessuch as Yarn'spnpmStoreFolder), whose bins are linked into each dependent's.bin, the listing alone still counts, as before.The #1791 repair (a linked bin an older release left with a CRLF shebang) still works; the on-disk LF-shebang evidence path (#1756) is unchanged. Wrappers under
npm/,pypi/,gem/don't touch this logic.Tests (red → green)
--no-bin-linkspatch::apply::tests::apply_writes_recorded_bytes_for_bin_npm_did_not_link(apply, re-apply,needs_npm_bin_rewrite, rollback byte-exact)directories.binfromfile:e2e_redirect_npm_build::npm_agent_keeps_crlf_shebang_of_bin_npm_did_not_link(npm 10.9.4)npm_bin::tests::{is_linked_bin_wants_npms_link, is_linked_bin_follows_a_symlink_to_this_file, is_linked_bin_finds_global_and_pnpm_links, is_linked_bin_looks_only_where_npm_links_the_copy, is_linked_bin_wants_the_shim_to_run_this_file, is_linked_bin_takes_the_listing_in_relocated_stores, bin_names_are_npms_link_names}Existing #1791 unit tests now put the package under
node_moduleswith a.binlink (the state npm leaves); the #1791/#1756 real-npm e2e tests pass unchanged.Local runs:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-cli --all-features --test e2e_redirect_npm_build -- --ignored: 9 passedcargo test --workspace --all-features --no-fail-fast: all pass except tests that can't pass in this sandbox:e2e_bun_lockb(2, need network to patches-api.socket.dev), and 9 permission-based tests (chmod-based write/remove failures; the sandbox runs as root). The 4 core ones fail identically onmainhere./code-review high
Fixed: nested copies were matched against the top-level
.bin(now nearestnode_modulesonly); virtual-store layouts (pnpm workspace members, npm linked, vlt, Bun) missed the link (now the listing counts there, with no store-wide scan); cheap disk probe now runs before the link lookup; trailing-slash bin keys; fixture forapply_keeps_crlf_shebang_npm_does_not_fixnow has a.binlink so it still checks the listing.Not changed:
installed_sourcebefore the link heuristic": a CRLF file hashing to the installed source is exactly the ambiguous After the #1756 fix, a CRLF-shebang npm bin that v5.0.1 already broke stays broken: apply reports "already patched", and repair and rollback keep the unrunnable CRLF shebang #1791 state (an older rollback wrote the raw CRLF original over a linked bin), so on-disk bytes can't settle it; the link is what tells the two apart.Bugbot
Two findings on 871630f, both fixed in d2387bf (threads answered and resolved): a plain file that only had the bin's name (a project's own
<bin>.cmd, or alibworkspace's siblingbin/<name>) counted as npm's link; and relocated stores lost the listing fallback. Bugbot's re-review of d2387bf: no issues found.https://claude.ai/code/session_011f9aeQu3zbzV1egVhwbLdK
Note
Medium Risk
Changes npm bin shebang logic used on every patch apply/rollback, with filesystem heuristics for
.binlinks and shims; regression risk is mitigated by broad new tests but edge-case layouts could still misclassify linked vs unlinked bins.Overview
Fixes #1804: patch apply and rollback no longer assume every
package.jsonbin was rewritten by npm’sfixBin. npm only normalizes shebangs on bins it links; with--no-bin-linksordirectories.binfrom afile:tarball install, on-disk bytes stay CRLF and must match the patch blobs exactly.is_listed_binis replaced byis_linked_bin, which checks for a real link (nearestnode_modules/.bin, global prefix shims, symlink/canonicalize, shim path matching) and still treats virtual-store layouts (pnpm, relocated stores) like before when links live on dependents.Apply/rollback write recorded
afterHash/beforeHashfor unlinked bins; linked-bin #1791 repair and LF-shebang heuristics are unchanged. Tests add unit coverage for link detection, apply+rollback byte-exact paths, refactored CRLF e2e helpers, and a real-npm e2e for both failure shapes.Reviewed by Cursor Bugbot for commit 5789c73. Configure here.
Generated by Claude Code