Repository navigation
Vendored mode still writes and deletes through a symlinked .socket dir: the #664 guard checks .socket/vendor and below only, so rollback in one yarn berry project wipes another project's vendored tarball and ledger #887
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
#666 (fixing #664) made every vendored write and revert refuse when .socket/vendor, .socket/vendor/<eco> or .socket/vendor/<eco>/<uuid> is a symlink (vendor_dir_symlink_unsupported). It doesn't check the level above: .socket itself. Two projects that share one .socket through a link (a/.socket -> ../sock, c/.socket -> ../sock) end up with the exact cross-project delete-through that #664 describes.
scan --mode vendoredin each project writes its<uuid>/unit andvendor/state.jsoninto the shared target, with exit 0 and no warning.rollback,vendor --revertorremove <purl>in project a deletes the sharedleft-pad-1.3.0.tgz,socket-patch.vendor.jsonandstate.json. It exits 0 withsuccess.- Project c's
package.jsonandyarn.lockstill point atfile:./.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz, so every freshyarn install --immutablefails YN0001 ENOENT. - c's
vendor --checkthen exits 0 (success,discovered: 0), because the shared ledger it would check is gone too.
Control: the same layout with the link one level lower (b/.socket/vendor -> ../../vend) is refused correctly with vendor_dir_symlink_unsupported, and nothing is written.
Impact
- Silent data loss in a different project: its committed artifacts and ledger are deleted by an unwind that reports success.
- That project's CI (
yarn install --immutable, cold or warm cache, sincefile:is read from disk) fails. The integrity check that should catch it (vendor --check) passes. - The two projects also share one
state.json, so each vendor run rewrites the other's ledger.
Repro (Linux, yarn 4.18.1, node-modules linker)
A local mock patch API serves a granted vendoring-service artifact (tarball plus yarn-berry-zip) for pkg:npm/left-pad@1.3.0. The yarnBerry10c0 is bootstrapped with a real yarn file: install.
mkdir -p r/sock && cd r
for p in a c; do
mkdir $p && (cd $p
echo "{\"name\":\"$p\",\"private\":true,\"dependencies\":{\"left-pad\":\"1.3.0\"}}" > package.json
printf 'nodeLinker: node-modules\nenableGlobalCache: false\n' > .yarnrc.yml
yarn install && ln -s ../sock .socket)
done
git init -q && git add -A && git commit -qm init
(cd a && socket-patch scan --mode vendored --json --yes --api-url $MOCK --api-token x --org org && yarn install) # exit 0
(cd c && socket-patch scan --mode vendored --json --yes --api-url $MOCK --api-token x --org org && yarn install) # exit 0
git add -A && git commit -qm vendored # sock/vendor/{state.json,npm/<uuid>/left-pad-1.3.0.tgz,…}
(cd a && socket-patch rollback --json --yes) # exit 0, success
git status --short # D sock/vendor/npm/<uuid>/left-pad-1.3.0.tgz, D …/socket-patch.vendor.json, D sock/vendor/state.json
git add -A && git commit -qm rollback-a
git clone -q . ../fresh && cd ../fresh/c && yarn install --immutable
# ➤ YN0001: │ Error: left-pad@file:./.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz::locator=c%40workspace%3A.: ENOENT …
cd ../../r/c && socket-patch vendor --check --json # exit 0, status success, discovered 0Expected vs actual
- Expected: the rule Fix vendored revert deleting through a symlinked vendor dir (#664) #666 introduced, quoted from the
vendor_dir_symlinkdoc (crates/socket-patch-core/src/vendor/path.rs:80-86): "Vendor staging creates these dirs itself and never writes symlinks, so a linked level is never ours: its target may be another project's vendor store … Every vendor and revert dispatch refuses on this before touching anything." A linked.socketis the same situation one level up, so the run should refuse withvendor_dir_symlink_unsupported, as it does for a linked.socket/vendor. Separately, CLI_CONTRACT saysvendor --checkverifies every vendored artifact, so a project whose lock is wired to a missing artifact shouldn't pass. - Actual: vendor and unwind both follow the
.socketlink. The unwind deletes the other project's unit and ledger with exit 0, and the other project'svendor --checkpasses.
OS × version
| OS | yarn | linker | unwind in project a | c's fresh --immutable |
c's vendor --check |
|---|---|---|---|---|---|
| Linux | 4.18.1 | node-modules | rollback (2 runs) |
YN0001 ENOENT | exit 0, discovered 0 |
| Linux | 4.18.1 | node-modules | remove pkg:npm/left-pad@1.3.0 |
YN0001 | exit 0 |
| Linux | 4.18.1 | pnpm | rollback |
YN0001 | exit 0 |
| Linux | 4.0.2 | node-modules | vendor --revert |
YN0001 | exit 0 |
| Linux | 4.18.1 | node-modules | control: .socket/vendor linked |
refused vendor_dir_symlink_unsupported, nothing written |
— |
| Linux | 4.18.1 | node-modules | control: .socket/vendor/npm linked |
refused, nothing written | — |
| macOS / Windows | — | — | not probed (this routine's probe branches are blocked on a branch-delete problem). The check is a symlink_metadata walk; on Windows a junction at .socket should behave the same |
Tested on main 9c43dfc. First bad: not a regression in the usual sense. Before #666 nothing was guarded; #666 left this level out.
Suspect code
crates/socket-patch-core/src/vendor/path.rs:93:vendor_dir_symlinkstarts itslevelsatVENDOR_DIR(.socket/vendor), so.socket(constants.rs:4,SOCKET_DIR) is never checked.crates/socket-patch-core/src/utils/socket_dir.rs:74(remove_tree_and_prune) is the delete that follows the link, as in Vendored yarn classic writes and deletes through a symlinked .socket/vendor/npm dir, so rollback in one project deletes another project's vendored tarballs and breaks its frozen install #664.- The root cause isn't yarn-specific. Per the triage on Vendored yarn classic writes and deletes through a symlinked .socket/vendor/npm dir, so rollback in one project deletes another project's vendored tarballs and breaks its frozen install #664, every vendored backend unwinds through
remove_tree_and_prune, so other ecosystems probably share it. I've only shown it with yarn berry.
Related: #664 / #666 (the .socket/vendor[/<eco>[/<uuid>]] levels, fixed), #627 / #802 (symlinked lockfiles, fixed), #831 (vendor --check passing after the vendored artifacts disappear).
Activity
yarn-berry: run 21 ledger (#887, #884, #859)
mikolalysenko commentedon Oct 6, 2026
[agent] Yarn Berry (2+) bug-hunt routine (ledger #305): the same symlinked
.socketalso affects agent mode. Nothing guards a linked.socketthere either.Linux, main
9c43dfc, yarn 4.18.1 node-modules. Projectsaandbeach have.socket -> ../shared:scan --mode agentin a, then in b: both patched (exit 0).rollbackin a: exit 0. The sharedmanifest.jsonis now{"patches": {}}, and the "unused" blobs are GC'd ("Freed 2.89 KB").applyprints "No patches to apply." (exit 0). Afterrm -rf node_modules && yarn install && socket-patch apply, b has the unpatchedleft-pad, still exit 0, andvexexits 2.So a rollback in one project silently unpatches every project sharing the link, with no warning. The fix for the vendored case (refusing a linked
.socket, or the whole.socketancestry, the wayvendor_dir_symlink_unsupportedrefuses.socket/vendor) probably needs to cover the manifest/blobs path too.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Yarn Berry (2+) bug-hunt routine (ledger #305), re-triage: the vendored defect is fixed on main
e2d9633by #1042 (ddc3bfb, "Guard .socket links and agent writes with one containment helper").Linux, yarn 4.18.1 node-modules. Projects
p1andp2each have.socket -> ../shared/.socket:get <uuid> --mode vendoredon main now refuses up front: exit 1,vendor_dir_symlink_unsupported(".socketis a symlink; …"), and nothing is written.vendor --revert,rollbackandremove <uuid>inp1on main. Each exits 1 (revert_failed,vendoredFailed,vendor_revert_failed) and deletes nothing.shared/.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgzandstate.jsonsurvive, andp2's wiring is intact.The agent-mode half from my earlier comment is unchanged.
rollbackin one project still empties the sharedmanifest.json, and the sibling'sapplythen says "No patches to apply." #1042's commit message names this deliberately ("Agent-mode state (manifest, blobs) under a linked .socket keeps working … the known gap that a linked .socket itself still redirects agent-mode blob and manifest writes"), so I'm treating it as acknowledged rather than as part of this issue.Closing, since the vendored data-loss path this issue tracks is fixed.
Generated by Claude Code
yarn-berry: run 32 ledger (#1131, #887 closed)