Repository navigation
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
Description
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
If .socket/vendor/npm is a symlink (for example, two non-workspace projects in a monorepo that share one vendor store), scan --mode vendored writes the <uuid>/ artifact dirs through the link. rollback / vendor --revert then delete through the link too (remove_tree_and_prune at crates/socket-patch-core/src/vendor/yarn_classic_lock.rs:548). When a second project has vendored the same patch into the same shared store, rolling back the first project deletes the second project's tarballs. The second project's yarn.lock still points at file:./.socket/vendor/npm/<uuid>/…tgz, so its next frozen or offline install fails. The rollback exits success with no warning.
The code already says this layout isn't ours. sweep_vendor_dirs (crates/socket-patch-core/src/vendor/path.rs ~357–364 and the note at ~892) is deliberately symlink-strict: "staging creates these dirs itself and never writes symlinks, so a symlinked eco dir cannot be ours — read_dir would follow it and the sweep would enumerate (and let callers delete through) its target." The vendor write path and the ledger-driven revert don't apply that rule, so the delete-through the sweep guards against still happens.
Impact
- Silent data loss in a different project: a project that is correctly vendored loses its committed artifacts, and the
rollbackthat did it reports success. - That project's
yarn install --frozen-lockfile [--offline]then fails (Tarball is not in network and can not be located in cache). Itsvendor --checkreportsartifact verification failed: Missing.
Repro (yarn 1.22.22; local mock patch API serving is-number@7.0.0 and left-pad@1.3.0 patches)
mkdir -p mono/shared && cd mono
for X in A B; do
mkdir $X && (cd $X
echo '{"name":"'$X'","version":"1.0.0","private":true,"dependencies":{"is-number":"7.0.0","left-pad":"1.3.0"}}' > package.json
yarn install
mkdir -p .socket/vendor && ln -s ../../../shared .socket/vendor/npm
socket-patch scan --mode vendored --vendor-source service --api-url $API --org test-org --api-token x --json --yes) # success
done
find shared -type f | wc -l # 4 (two uuid dirs: marker + tgz each)
(cd A && socket-patch rollback --json --yes) # status: success, no warning
find shared -type f | wc -l # 0 <- B's artifacts are gone too
cd B && rm -rf node_modules && yarn install --frozen-lockfile --offline
# error "./.socket/vendor/npm/1111…/is-number-7.0.0.tgz": Tarball is not in network and can not be located in cache (exit 1)
socket-patch vendor --check --json # partialFailure: artifact verification failed: Missing (x2)vendor --revert in A instead of rollback gives the same result.
Expected vs actual
- Expected: a symlinked
.socket/vendor/<eco>(or<uuid>) dir is refused before any write, as the other symlinked vendored inputs are. Hostedredirect_symlinked_file_unsupported, the cargo / pypi*_symlink_unsupportedrefusals and the bun / hatch backends' own checks all refuse before writing (CLI_CONTRACT error table). At minimum, a revert must never delete through a link the sweep treats as "cannot be ours". - Actual: vendoring writes through the link, and the revert deletes the uuid dirs at the link's target. Exit 0 /
successboth ways.
Matrix (Linux, main 045d7ec, Node 22)
| yarn | A + B vendored | A rollback empties the shared store |
B frozen offline install afterwards |
|---|---|---|---|
| 1.7.0 | success | yes | fails (exit 1) |
| 1.10.1 | success | yes | fails (exit 1) |
| 1.22.22 | success (x2) | yes (x2, also via vendor --revert) |
fails (exit 1) |
Single project with a symlinked .socket/vendor/npm: the scan, the fresh frozen install, vendor --check and rollback all "work" (the artifacts live at the link target). The damage only shows when the store is shared. A whole-.socket symlink shares vendor/state.json too, so that's a different shape; it wasn't tested for cross-project effects.
Suspect code
crates/socket-patch-core/src/vendor/yarn_classic_lock.rs:548:remove_tree_and_prune(&uuid_dir, …)with no lstat check on.socket/vendor/npmor on<uuid>.- The vendored staging and write path for
.socket/vendor/<eco>/<uuid>/has no symlink gate. The other npm-family backends (npm_lock,pnpm_lock,bun_lock,vlt_lock) and composer / nuget use the sameremove_tree_and_prune(&uuid_dir, …)pattern, so this probably isn't specific to yarn classic. Related: Vendored yarn classic replaces a symlinked yarn.lock with a regular file (hosted refuses the same lock), leaving the link's target unpatched; rollback never restores the link #627 (symlinkedyarn.lockreplaced by vendored mode).
No probe run: the behaviour comes from a path-handling choice that doesn't depend on the OS. macOS / Windows (directory junctions) are untested.
Activity
yarn-classic: run 11 ledger (#664, #665) + npm handover
mikolalysenko commentedon Oct 3, 2026
[agent] Triage: p1 (yarn classic). The defect is wider than yarn classic. Every vendored backend removes its unit with
utils::socket_dir::remove_tree_and_prune(npm, pnpm, yarn classic, yarn berry, bun, vlt, composer, nuget, maven, gem, pypi), and that helper doesn't check whether.socket/vendor/<eco>or<uuid>is a symlink, thoughvendor::path::sweep_vendor_dirstreats a symlinked eco dir as never ours. So the cross-project delete-through applies to every ecosystem's revert. Not a duplicate.Generated by Claude Code
mikolalysenko commentedon Oct 3, 2026
[agent] Claiming this issue (shared root cause: vendored writes and unit removal follow a symlinked
.socket/vendor/<eco>or<uuid>dir). Branch: agent/fix-vendor-dir-symlink-guard. Claim-ID: 2026-10-03T07:21:05Z-534db9Generated by Claude Code
mikolalysenko commentedon Oct 3, 2026
[agent] Draft PR: #666
Generated by Claude Code
Refuse vendoring through a linked vendor dir
Stop repair redownloading into a linked store
Fix vendored revert deleting through a symlinked vendor dir (#664) (#666
Merge main into fix-npm-tarball-gitignore
Merge origin/main into agent/fix-vendored-symlink-write-gate
Fix tests broken by the feat/gradle-support merge