Repository navigation
Fix npm v4 lock refusal advice looping (#1576) - #1651
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Vendoring an npm project whose package-lock.json is lockfileVersion 4 (written by npm >= 12.1 for `npm patch`) is refused, and the refusal told users to drop `patchedDependencies` and run `npm install`. npm keeps an existing v4 lock at v4, so following that advice refused again with the same text, forever. The refusal now names `npm install --lockfile-version 3`, the step that actually rewrites the lock as v3. A v4 lock with no native patch left no longer claims vendoring would break a patch it does not have. Fixes #1576 Assisted-by: Claude Code:claude-opus-5-5
The v4 refusal now treats a project as still carrying an `npm patch` when package.json has a `patchedDependencies` key, even if no lock entry records it, and ignores a `"patched": null` record. That is how hosted mode already reads it, so a user who kept the key but lost the lock record is still told to remove it before downgrading the lock. Refs #1576 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Unguarded package.json read can hang
- Replaced bare std::fs::read_to_string with read_regular_to_string_sync to prevent FIFO/device blocking in has_native_patch.
Or push these changes by commenting:
@cursor push 73613062ab
Preview (73613062ab)
diff --git a/crates/socket-patch-core/src/vendor/npm_lock.rs b/crates/socket-patch-core/src/vendor/npm_lock.rs
--- a/crates/socket-patch-core/src/vendor/npm_lock.rs
+++ b/crates/socket-patch-core/src/vendor/npm_lock.rs
@@ -22,7 +22,7 @@
use crate::constants::SOCKET_DIR;
use crate::manifest::schema::PatchRecord;
use crate::patch::apply::PatchSources;
-use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_bytes};
+use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_bytes, read_regular_to_string_sync};
use crate::utils::socket_dir::remove_tree_and_prune;
use super::common::{parse_json_manifest, refused, JsonLayout};
@@ -487,7 +487,7 @@
/// `patchedDependencies` key, or a non-null `patched` record on a lock
/// entry. Only the refusal path calls it, so the blocking read is rare.
fn has_native_patch(lock: &Value, project_root: &Path) -> bool {
- let manifest = std::fs::read_to_string(project_root.join("package.json")).ok();
+ let manifest = read_regular_to_string_sync(&project_root.join("package.json")).ok();
if !super::bun_lock_text::patched_dependency_keys(manifest.as_deref(), None).is_empty() {
return true;
}You can send follow-ups to the cloud agent here.
The v4 refusal's native-patch probe read package.json with a plain open, so a FIFO or device at that path would hang the vendored run forever. It now goes through the regular-file reader the rest of the vendor code uses, which refuses non-regular files without blocking. Refs #1576 Assisted-by: Claude Code:claude-opus-5-5
|
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 2b1c49e. Configure here.
|
Labeled Ready for review by the burn-down agent.
Generated by Claude Code |
Final review brief (for the human approver)What it does Risk: low. It only changes the detail text on a path that already refuses. The one new I/O is a read of the root Look here
Verified
Open questions (non-blocking)
Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |

LLM Description written by Claude Code:claude-opus-5-5
Fixes #1576
Summary
Vendoring an npm project whose
package-lock.jsonislockfileVersion: 4(written by npm >= 12.1 for its nativenpm patch) is refused withvendor_lockfile_version_unsupported. The refusal told users to removepatchedDependenciesand "regenerate the lock withnpm install". npm keeps an existing v4 lock at v4 on a plain install, so following the advice refused again with the same text, forever.Now:
npm install --lockfile-version 3, the step that rewrites the lock as v3 and clears the refusal (as the issue's matrix shows for npm 12.1 and 12.2).patchedDependencieskey in package.json, no non-nullpatchedrecord in the lock) gets a detail that names only that downgrade, instead of claiming vendoring "would drop or break that patch".patchedDependenciesalso patches, so every laternpm ci/npm installfails EPATCHFAILED (and vendored refuses the lockfileVersion 4 lock with wrong advice) #711 gate reads it (bun_lock_text::patched_dependency_keyson package.json, plus non-nullpatchedrecords), so the two modes agree.docs/testing/npm-compatibility.mdandCLI_CONTRACT.mddescribe the new remedy.The refusal code is unchanged; only the detail text differs.
Root cause
lock_version_gate(crates/socket-patch-core/src/vendor/npm_lock.rs) gave every v4 lock the same remedy, and that remedy (npm install) cannot change the lock version. The comment right above the text already said so.Tests
vendor::npm_lock::tests::lockfile_v4_manifest_patch_remedy_downgrades_the_lockvendor::npm_lock::tests::lockfile_v4_without_native_patch_names_only_the_downgradevendor::npm_lock::tests::native_patch_probe_does_not_block_on_a_fifo_manifestvendor::npm_lock::tests::lockfile_v4_refusal_names_npm_patchCommands run locally:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-core --all-features --lib lockfile_v4: 3 passed. With only the tests applied onmain's gate, 2 failed.cargo test --workspace --all-features --no-fail-fast: everything that ran passed except 8 tests that fail only because this sandbox runs as root (permission-denial fixtures such ascopy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry) or can't reach patches-api.socket.dev (e2e_bun_lockb). None of them touchnpm_lock.rs. The run hit its 10-minute cap before the last CLI suites; CI covers them.Code review notes
Bugbot (on b3eaff4) flagged the plain
std::fs::read_to_stringof package.json as able to block on a FIFO; fixed in 2b1c49e by reading throughutils::fs::read_regular_to_string_sync./code-review highran on the diff. Fixed: package.jsonpatchedDependenciesnow counts,"patched": nulldoesn't, CLI_CONTRACT updated, duplicate test folded into the #711 one. Not changed:npm-shrinkwrap.json: npm >= 12 never reads or writes the shrinkwrap, so npm 12 can't produce one in v4. It would take a hand-renamed file, which is too unlikely to be worth its own branch.Generated by Claude Code