Skip to content

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 upstream

redirect.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_skipped on a block the vendored ledger wired), retract the purl. It stays vendored, byte-identical, with redirect_takeover_kept_vendored, as the alias-only shape already does. Failing that, at least don't announce redirect_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:285 Takeover::unpinned treats 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:3709 is where the classic rewriter records alias_skipped for 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

  1. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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::unpinned in scan/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

  2. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  3. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1180


    Generated by Claude Code

  4. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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:

    shape main 03b9418 PR #1180 46f1bc0
    left-pad + lp: npm:left-pad@1.3.0, vendored → hosted takeover redirect_takeover_reverted_vendored; fresh frozen install has node_modules/lp unpatched redirect_takeover_kept_vendored; both copies patched after a fresh frozen install
    scoped alias @my/lp: npm:left-pad@1.3.0, same flow (not run on main) redirect_takeover_kept_vendored; both copies patched
    plain hosted scan (no prior vendoring), same deps pins nothing, exit 0: redirect_unattributable (the #1058 gate), so the direct copy also stays unpatched direct block pinned, alias skipped (redirect_yarn_classic_alias_skipped); in-run --vex omits the package (vex_omitted) — #1081 fixed

    So 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

  5. added
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.
    on Oct 9, 2026
  6. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentcompatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.pm:yarn-classicYarn classic (1.x)priority:p1v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions