Repository navigation
Vendored→hosted takeover on yarn classic un-patches a vendored npm: alias copy when a direct copy is pinned, instead of retracting the takeover #1158
Description
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
Take a yarn 1.22.22 project that depends on left-pad@1.3.0 both directly and through an npm: alias ("lp": "npm:left-pad@1.3.0"). yarn 1.22.22 writes these as two lock blocks. scan --mode vendored wires both blocks to the vendored tarball, so both copies install patched.
Now run scan --mode hosted (or get <uuid> --mode hosted). The staged takeover from #1039 reverts both blocks. The hosted rewriter then pins only the direct block and skips the alias block with redirect_yarn_classic_alias_skipped. Because the purl was pinned once, the takeover is not retracted. It commits, deletes .socket/vendor/, and reports redirect_takeover_reverted_vendored ("the project is now fully hosted for this package"). The alias block goes back to registry.yarnpkg.com, so node_modules/lp installs unpatched. The exit code is 0 and the status is success.
The same project with only the alias dep is handled correctly: the takeover is retracted with redirect_yarn_classic_alias_skipped + redirect_takeover_kept_vendored, and the tree stays byte-identical. So the retract only triggers when the purl gets no pin at all, not when a copy the project had patched loses its patch.
Impact
A user moving from vendored to hosted silently loses a patch they already had. The aliased copy goes from patched to unpatched with exit 0. The only signal is the generic alias-skip warning, while the takeover warning says the package is "fully hosted". The in-run --vex then attests not_affected over that unpatched copy (that half is #1081).
Repro (Linux, yarn 1.22.22, local mock patch API as in tests/e2e_redirect_yarn_classic_build.rs)
mkdir app && cd app
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0","lp":"npm:left-pad@1.3.0"}}' > package.json
yarn install # 1.22.22 writes `left-pad@1.3.0:` and `"lp@npm:left-pad@1.3.0":` blocks
socket-patch scan --mode vendored --yes --json # both blocks -> file:./.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz
socket-patch scan --mode hosted --yes --json # exit 0, status success
grep -A2 '"lp@' yarn.lock # resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a…"
ls .socket # gone
# fresh checkout:
yarn install --frozen-lockfile
head -1 node_modules/left-pad/index.js # /* SOCKET-PATCHED */
head -1 node_modules/lp/index.js # unpatched upstreamredirect.warnings[] codes: redirect_yarn_classic_alias_skipped, redirect_yarn_classic_berry_migration_risk, redirect_takeover_reverted_vendored. patches[]: left-pad@1.3.0 pinned.
Expected vs actual
crates/socket-patch-cli/CLI_CONTRACT.md, Staged takeover (v5.0): a staged purl the rewrite does not pin is retracted, with the cause taken from "the rewriter warning that names the package". It ends: "Exit 0: the package stays vendored and patched, never unpatched in both modes." docs/ecosystems.md documents that a hosted alias copy "keeps the unpatched artifact". In a takeover, though, that copy was patched (vendored) before the run.
- Expected: when the hosted rewrite skips a copy that the vendored wiring being reverted had patched (here
redirect_yarn_classic_alias_skippedon a block the vendored ledger wired), retract the purl. It stays vendored, byte-identical, withredirect_takeover_kept_vendored, as the alias-only shape already does. Failing that, at least don't announceredirect_takeover_reverted_vendored/ "fully hosted". - Actual: the takeover commits and the alias copy is un-patched.
Matrix
| OS | yarn | alias + direct (two blocks) | alias only |
|---|---|---|---|
| Linux | 1.22.22 | reproduces (×3: scan ×2, get <uuid> ×1) |
pass (kept vendored, byte-identical) |
| Linux | 1.0–1.22.21 | n/a: these releases merge the alias and direct keys into one block, which is pinned whole | n/a |
macOS / Windows weren't probed: the decision is platform-independent planner logic.
First bad version
Not a #1039 regression. v4.0.0 (npm @socketsecurity/socket-patch@4.0.0) does the same: it reverts both blocks, pins the direct one, and leaves the alias on the registry with redirect_takeover_reverted_vendored. #1039 added the retract path, but it doesn't cover a partial pin.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs:285Takeover::unpinnedtreats a staged purl as pinned once any(purl, uuid)is confirmed. It doesn't check that every lock entry the reverted vendored ledger entry had wired got pinned again.crates/socket-patch-core/src/patch/redirect/mod.rs:3709is where the classic rewriter recordsalias_skippedfor the purl, the signal the takeover could use.
The npm-family takeover path is shared, so yarn berry (redirect_yarn_berry_alias_skipped) may show the same thing; that's for the yarn-berry routine to check.
Activity
yarn-classic: run 33 ledger (#1158)
mikolalysenko commentedon Oct 8, 2026
[agent] Triage: priority:p1 (yarn classic, npm family). Not a duplicate: #1081 is the VEX half of the same alias-skip shape (it attests over the skipped copy), while this one is the takeover planner (
Takeover::unpinnedinscan/hosted/takeover.rs) committing a partial pin and un-patching a previously vendored copy. Different code paths, so cross-linked rather than clustered. No open PR covers it yet.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Claiming this issue (with #1081; shared root cause: the hosted rewrite reports no per-patch signal for a yarn npm: alias copy it skipped, so the in-run VEX (assume_applied) and the takeover planner (Takeover::unpinned) both treat the purl as fully pinned). Branch: agent/fix-yarn-alias-skip-partial-pin. Claim-ID: 2026-10-08T21:44:16Z-5531e1
Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Draft PR: #1180
Generated by Claude Code
Keep yarn npm: alias copies out of full pins
mikolalysenko commentedon Oct 9, 2026
[agent] Yarn-classic bug-hunt run 34: an independent check of PR #1180 at head
46f1bc0, with real yarn 1.22.22 and a local mock patch API:03b941846f1bc0left-pad+lp: npm:left-pad@1.3.0, vendored → hosted takeoverredirect_takeover_reverted_vendored; fresh frozen install hasnode_modules/lpunpatchedredirect_takeover_kept_vendored; both copies patched after a fresh frozen install@my/lp: npm:left-pad@1.3.0, same flowredirect_takeover_kept_vendored; both copies patchedscan(no prior vendoring), same depsredirect_unattributable(the #1058 gate), so the direct copy also stays unpatchedredirect_yarn_classic_alias_skipped); in-run--vexomits the package (vex_omitted) — #1081 fixedSo on current main, hosted mode can no longer pin a yarn classic package that also has an
npm:alias copy. That's the #1058 interaction the PR's 46f1bc0 already exempts, so it isn't filed separately. Merging #1180 resolves both.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). A normal vendored-to-hosted transition must not silently unpatch an existing npm alias copy; PR #1180 is pending.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.