Repository navigation
Fix vendored revert deleting through a symlinked vendor dir (#664) - #666
Conversation
Assisted-by: Claude Code:claude-opus-5-5
When two projects shared one vendor store through a symlinked .socket/vendor/<eco> dir, rolling back one project deleted the other project's committed artifacts, and the rollback still reported success. The other project's next frozen install then failed. socket-patch never creates links under .socket/vendor, so a linked .socket/vendor, <eco> or <uuid> dir is not ours. Vendoring now refuses it with vendor_dir_symlink_unsupported before writing, and every vendored revert fails on the same check before it edits a lock or deletes anything. The shared unit-removal helper also refuses to delete through a link, and the stale pre-stage sweep and the vendor dir sweep no longer follow a linked vendor dir. Fixes #664 Assisted-by: Claude Code:claude-opus-5-5
repair's artifact redownload wrote into .socket/vendor/<eco>/<uuid> through a symlinked vendor dir, landing bytes in a store another project may own. It now refuses with vendor_dir_symlink_unsupported before downloading. Also adds a real yarn classic e2e for the two-project shared-store rollback from #664. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The JVM symlink-escape test expected its backend's own build_file_outside_root reason for a linked .socket/vendor. The new up-front gate now refuses that case for every ecosystem with vendor_dir_symlink_unsupported, still before writing anything. The .mvn and module-dir cases keep their JVM reason. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A jvm ledger entry is reverted by the maven backend, and maven-family entries can own files in the JVM repository tree under .socket/vendor/maven2. The linked-dir check only looked at .socket/vendor for jvm entries, so a linked maven or maven2 dir got past dispatch and the lock was restored before the delete was refused. The check now covers maven, maven/<uuid> and maven2 for both. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Gradle JVM entries write and revert files under .socket/vendor/gradle, so a shared linked gradle dir could still be deleted through by a rollback. The JVM vendor trees are now one constant that both the tree-write allowlist and the linked-dir check read, so maven and jvm entries check maven2 and gradle. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review at
Generated by Claude Code |
|
Codex follow-up review of The author's correction refuses linked vendor directories before hosted restoration, excludes those targets from service prefetch, and retains the dispatcher backstop. The latest commit removes unrelated formatting changes, leaving 11 files in the PR. All 130 files in that cleanup reproduce the previously validated Validation retained from the tested source:
411 successful checks, 7 skipped, and 11 successful workflows (1 additional workflow skipped). Bugbot is clear on this commit; no unresolved review threads or new actionable findings. Ready for review has been restored. GitHub still requires the normal human approval before merge. |
The linked-dir guard ran inside dispatch, after the vendor loop had already restored a live hosted pin's upstream entry. A scan --mode vendored over a hosted project with a linked .socket/vendor/npm then failed with the hosted patch already unwired: the lock and .npmrc had changed and no vendored artifact replaced them. The loop now refuses with vendor_dir_symlink_unsupported before the takeover, and the service prefetch plan leaves such purls out, so no download grant is spent on them. Dispatch keeps the same check as a backstop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHr7bqqwKnGKwkSCCcdWrN
|
Fixed the Codex P1 in Regression test: Ran locally on To whichever session is preparing its own correction: this push covers the ordering case, so please rebase onto Generated by Claude Code |
|
BugBot review Generated by Claude Code |
Pins the prefetch half of the ordering fix: plan_service_downloads leaves out a package whose vendor unit is a link, so a prefetch running ahead of the loop never stages or extracts an archive through it. The test fails without the exclusion and passes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHr7bqqwKnGKwkSCCcdWrN
|
The second Codex point (background prefetch extracting through a linked unit) is covered by the prefetch-plan exclusion in Generated by Claude Code |
|
BugBot review Generated by Claude Code |
Main is not rustfmt-clean under the pinned 1.93.1 toolchain, so the cargo fmt --all in 5db2fd3 reformatted 127 files this fix never touches, and reformatted untouched code in the 11 files it does. This puts those files back to main's bytes and keeps only the fix's own edits. CI does not run rustfmt, and the code is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHr7bqqwKnGKwkSCCcdWrN
|
Re-ran on 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 356084b. Configure here.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #664
Summary
If two projects share one vendor store through a symlinked
.socket/vendor/npm(or any<eco>/<uuid>level), rolling back one project deleted the other project's committed artifacts, and the rollback still exitedsuccess. The other project's next frozen or offline install then failed. Vendoring also wrote through the link. Now socket-patch refuses to write, delete or redownload through a linked vendor dir, and says which path is the link.Root cause
Vendor staging creates
.socket/vendor/<eco>/<uuid>/itself and never writes symlinks, so a linked level isn't ours.vendor::path::sweep_vendor_dirsalready applied that rule, but the write and delete paths didn't:utils::socket_dir::remove_tree_and_prune, which followed the link (npm, pnpm, yarn classic, yarn berry, bun, vlt, composer, nuget, maven, gem, pypi). So this wasn't yarn classic only;vendor::redownload::restore(repair) andvendor::prestage::sweep_stalealso went through a linked eco dir.Changes
vendor::path::vendor_dir_symlink: one check returning the first linked level among.socket/vendor,<eco>and<uuid>.dispatch_vendor_onerefusesvendor_dir_symlink_unsupportedbefore any backend write.dispatch_revert_one_opts, which handlesvendor --revert,rollback,removeand the vendored→hosted takeover, fails before any lock edit or delete. The lock and ledger stay as they were.remove_tree_and_prunerefuses to delete whendiror any level between it andstop_diris a link. This protects every caller even if a dispatcher misses the check. Links at or above.socket(for example/tmp -> /private/tmp) are still allowed.mavenandjvmentries the check also covers the JVM vendor trees.socket/vendor/maven2and.socket/vendor/gradle(one shared constant,jvm::apply::VENDOR_TREES), because ajvmentry is reverted by the maven backend.in_process_vendor::hosted_to_vendor_conversion::hosted_then_vendor_over_a_linked_vendor_dir_keeps_the_hosted_wiring.redownload::restorerefuses before downloading.prestage::sweep_staleandsweep_vendor_dirsskip a linked.socket/vendor/ eco dir.vendor_dir_symlink_unsupported.A whole-
.socketsymlink, which also sharesstate.json, is out of scope, as the issue notes.Test evidence
Per-issue checklist:
in_process_rollback_vendored::rollback_never_deletes_through_a_shared_vendor_dir_linkandvendor_refuses_a_linked_vendor_dir_before_writing. Both fail on main (rollback exits 0 and the shared tarball is deleted; vendor stages into the link target) and pass with the fix. The real-yarn twine2e_vendor_yarn_classic_build::yarn_classic_rollback_keeps_a_shared_vendor_store_intactreplays the issue's repro: A's rollback fails naming the link, and B'syarn install --frozen-lockfile --offlinewith an empty cache still installs the patched bytes.path::vendor_dir_symlink_finds_the_outermost_linked_level,path::sweep_never_follows_a_symlinked_vendor_dir,socket_dir::remove_tree_and_prune_never_deletes_through_a_link,socket_dir::remove_tree_and_prune_allows_a_linked_project_path,prestage::sweep_never_follows_a_linked_ecosystem_dir,redownload::linked_vendor_dir_refuses_without_downloading.Commands run locally on
736712a:cargo fmt --all -- --check: ok.cargo clippy --workspace --all-features -- -D warnings: ok.cargo test -p socket-patch-core --all-features --lib: 4848 passed, 4 failed. The 4 failures are read-only-permission tests that can't fail when run as root (this sandbox is uid 0):copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files. None of them touch the changed code; CI runs as non-root.cargo test -p socket-patch-cli --all-featureswith--lib(834),--test in_process_rollback_vendored(8),--test e2e_vendor_yarn_classic_build(12,SOCKET_PATCH_YARN_E2E_REQUIRED=1, yarn 1.22.22),--test in_process_vendor(104),--test vendor_eject(4),--test vendor_jvm_cli(12),--test scan_vendor_e2e(33): all pass.cargo test --workspacehere: linking every test binary uses up this session's disk allowance. CI runs the full suite.npm/,pypi/andgem/are unchanged; they only dispatch to the binary.🤖 Generated with Claude Code
Generated by Claude Code