Repository navigation
Fix CRLF npm bins left broken by v5.0.1 (#1791) - #1792
Merged
Mikola Lysenko (mikolalysenko) merged 3 commits intoOct 28, 2026
Merged
Conversation
Assisted-by: Claude Code:claude-opus-5-5
v5.0.1 could write a patch's raw bytes over an npm bin that npm had installed with an LF shebang, leaving "#!/usr/bin/env node\r" that env can't run (exit 127). After #1757, upgrading did not recover it: apply and apply --check called it already patched, and rollback wrote the CRLF original back, because the shebang form was chosen from whatever was on disk. Decide from the package's own package.json bin field (bin string or object, or directories.bin) whether a file is a bin npm rewrites. Apply now rewrites such a bin in npm's form, apply --check reports it as not applied, and rollback restores npm's runnable form of the original. Fixes #1791 Assisted-by: Claude Code:claude-opus-5-5
Read package.json bins the way npm does: array bins, a string bin only when the package has a name, and directories.bin (skipping hidden files) when bin is missing or empty. apply --check now flags a bin only when its bytes are exactly the patched content, the same test apply uses, so the two always agree. Rollback also rewrites a bin that an older rollback left holding the raw CRLF original, when the original's blob is available. Writes skip reading package.json for content that has no CRLF shebang. Refs #1791 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 28, 2026 20:05
Collaborator
Author
|
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 bb8b488. Configure here.
Collaborator
Author
|
Ready for review at
Generated by Claude Code |
Tanmay Singla (Tanmay182003)
approved these changes
Oct 28, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-npm-bin-crlf-recovery
branch
October 28, 2026 21:56
This was referenced Oct 29, 2026
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 29, 2026
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
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.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1791
Summary
Users whose npm bin was broken by v5.0.1 (a CRLF shebang that exits 127) can now recover by upgrading and running
applyorrollback. Before this change, both commands reported success and left the bin broken.applyrewrites an npm bin that holds the raw patched bytes with a CRLF shebang into npm'sfixBinform (LF shebang). The bin runs again, and a secondapplyis a no-op.apply --checkreports such a bin as drift (not_applied, exit 1) instead of "Patches are in sync".rollbackfrom that state restores npm's runnable form of the original, not the CRLF tarball bytes.rollbackalso rewrites a bin that an older rollback left holding the raw CRLF original, as long as the original's blob is present. Without the blob it stays "already original", as before.Root cause
npm_bin::form_for_writedecided whether a file was a bin npm had installed by looking at the shebang currently on disk: only a file that already had an LF shebang counted. A bin that v5.0.1 overwrote with the raw CRLFafterHashbytes failed that test, sorollbackwrote the CRLF original back. Separately, the apply engine returnedAlreadyPatchedas soon as the raw bytes hashed toafterHash, and theapply --checkverifier agreed.Fix
The bin decision now comes from the package's own
package.json, read the way npm'snormalize-package-binanddirectories.binstep read it:binas a string (only when the package has aname), an object's values, or an array's entries;binis missing or empty, non-hidden files underdirectories.bin;./and..folded).The new logic lives in
npm_bin::is_listed_binandnpm_bin::needs_fix, which also requires the bytes to be exactly the recorded content. It is used by:form_for_write, for both apply and rollback. Content with no CRLF shebang skips all of this I/O.mark_crlf_bin_rewritein the apply engine and in rollback. The sharedverify_file_patchis unchanged, because a vendored tarball's staging copy legitimately holds the CRLF bytes.apply --check, through the newpatch::apply::needs_npm_bin_rewrite, which uses the sameafterHashcondition as apply so the two always agree.The earlier LF-shebang heuristic is kept as a fallback for packages with no readable
package.json. Vendored npm flows patch tarballs in memory and never reachform_for_write, so they are unaffected.Per-issue checklist (#1791)
applysays "already patched", bin exits 127patch::apply::tests::apply_repairs_crlf_bin_left_by_raw_write; e2enpm_agent_recovers_crlf_bin_left_by_raw_writeapply --checksays "in sync"npm_agent_recovers_crlf_bin_left_by_raw_write(exit 1 beforeapply, 0 after)rollbackwrites the CRLF originalpatch::rollback::tests::rollback_of_raw_crlf_bin_restores_npm_form; same e2eapply_keeps_crlf_shebang_npm_does_not_fix,npm_bin::tests::lists_bin_reads_every_bin_shaperollback_rewrites_raw_crlf_original_binTest evidence
Red → green:
cargo test -p socket-patch-core --lib rollback_of_raw_crlf_binfailed: the file stayed CRLF.npm_agent_recovers_crlf_bin_left_by_raw_writefailed at its first assertion:apply --checkprinted "Patches are in sync (1 patch checked)." and exited 0 (expected 1).Commands run locally (Linux, npm 10.9.4, Node 22):
cargo fmt --all -- --checkandcargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features: 6876 passed, 4 failed. All 4 are read-only-permission tests (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_…,pypi_requirements::wire_failure_rolls_back_…). They fail only because this container runs as root, which ignores the read-only bits. They are untouched by this PR and pass on CI's unprivileged runners.cargo test -p socket-patch-cli --all-features --lib --bins: 936 passed.cargo test -p socket-patch-cli --all-features --test apply --test e2e_redirect_npm_build -- --include-ignored: 157 + 38 passed.cargo test --workspace --all-featuresneeds more disk than this container allows, so the remaining CLI integration suites are left to CI.npm/,pypi/,gem/) only dispatch to the binary; no change was needed there.Review notes (
/code-review high)Fixed in bb8b488:
--checknow uses apply'safterHashcondition.binis read the way npm reads it (arrays, nameless string, empty-binfallback, hidden files).form_for_writechecks the content before any I/O.Not changed:
vexstill attests a bin in the v5.0.1 state. The patched code is on disk byte for byte, so the vulnerability is fixed; the bin just can't start. Making the shared verifier fail it would drop real attestations for everyvexand stale-install caller.apply --checkis where that drift is reported.fixBin(e.g. yarn classic) now get the LF form for a listed bin. A CRLF shebang never runs underenveither way, and every verifier already accepts npm's form of both hashes.--check. It only reads a 2 KB prefix per file, andpackage.jsononly for a CRLF-shebang candidate.bininsidepackage.jsonat the same time as the bin. Not seen in practice.repairis unchanged. CLI_CONTRACT defines it as blob/artifact repair that never touches installed files.applyis the command that converges installed files, and it now does.🤖 Generated with Claude Code
https://claude.ai/code/session_011f9aeQpwwNbXABKDVsjWva
Note
Medium Risk
Changes apply, rollback, and check behavior for npm
binfiles across real installs; scope is limited to listed bins with fixable CRLF shebangs, but incorrect bin detection could rewrite or skip the wrong files.Overview
Fixes #1791: npm bins that still hold raw patched bytes with a CRLF shebang (from v5.0.1) are treated as broken even when the hash matches
afterHash.applyandrollbacknow rewrite listed npmbinfiles into npm’sfixBin(LF shebang) form, usingpackage.jsonbinmetadata (with the old LF-on-disk heuristic as fallback) instead of trusting the on-disk shebang alone.apply --checkdemotes packages that would still be rewritten to drift (not_applied), aligned with the apply engine vianeeds_npm_bin_rewrite. Verification still reportsAlreadyPatchedfor tarball staging copies that legitimately keep CRLF bytes.E2E coverage is refactored around shared CRLF-bin fixtures and adds
npm_agent_recovers_crlf_bin_left_by_raw_writefor check → apply → rollback recovery.Reviewed by Cursor Bugbot for commit bb8b488. Configure here.
Generated by Claude Code