Skip to content

After yarn remove of a vendored package, rollback fails forever (exit 1) and no command can clean up the orphaned yarn classic artifact; the remedies it prints don't work #665

Description

[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).

Summary

Vendor a yarn classic project, then drop one patched dependency with a plain yarn remove <pkg>, a routine dev change. Yarn deletes that package's yarn.lock block, so nothing references .socket/vendor/npm/<uuid>/ any more. socket-patch treats the missing block as drift (vendor_lock_entry_drifted: lock block \is-number@7.0.0` no longer exists; nothing to restore), keeps the artifact and the ledger entry (vendor_artifact_kept`), and then no command can get out of that state:

command result exit
rollback partial_failure, every time it runs 1
vendor --revert success (yet the artifact and the ledger entry stay) 0
remove pkg:npm/is-number@7.0.0 vendor_revert_kept: "re-run scan --mode vendored to normalize, then remove" 1
scan --mode vendored (the remedy above), then remove nothing normalized; remove fails the same way 1
repair success, no-op 0
vendor --check vendor_check_ok for a package no longer in the lock 0

The vendor_artifact_kept remedy ("undo the drift (restore the vendored lock entries or re-vendor) and re-run vendor --revert") can't be followed either: the dependency is gone, so there's nothing to re-vendor, and restoring the block means undoing the user's yarn remove. The only way out is deleting .socket/vendor/npm/<uuid> and editing state.json by hand.

Impact

  • socket-patch rollback exits 1 permanently in any vendored yarn classic project where a patched dependency was later removed, which breaks CI or uninstall scripts that run it.
  • A stale vendored tarball stays committed forever. vendor --check reports it as OK.
  • vendor --revert and rollback disagree about the same state (exit 0 vs exit 1).

Repro (yarn 1.22.22; local mock patch API serving is-number@7.0.0 and left-pad@1.3.0 patches)

mkdir p && cd p
echo '{"name":"p","version":"1.0.0","private":true,"dependencies":{"is-number":"7.0.0","left-pad":"1.3.0"}}' > package.json
yarn install
socket-patch scan --mode vendored --vendor-source service --api-url $API --org test-org --api-token x --json --yes   # success
yarn remove is-number                       # yarn.lock no longer mentions the uuid
socket-patch rollback --json --yes; echo $?          # partial_failure, 1 (vendor_lock_entry_drifted + vendor_artifact_kept)
socket-patch rollback --json --yes; echo $?          # still 1
socket-patch scan --mode vendored --vendor-source service ... ; socket-patch remove pkg:npm/is-number@7.0.0 --json --yes; echo $?   # vendor_revert_kept, 1
ls .socket/vendor/npm/1111*/                # is-number-7.0.0.tgz  socket-patch.vendor.json  (still there)

Expected vs actual

  • Expected: a lock block that no longer exists isn't a third-party re-resolution that needs protecting. The artifact can't be "needed for a later restore", because nothing resolves through it. The yarn classic revert already has the probe for this (lock_text_mentions_uuid, yarn_classic_lock.rs ~525). CLI_CONTRACT documents the same rule for the backends that can't tell drift apart: "a file that no longer references it is warned about and the artifact removed" (composer / maven / nuget; gem removes the artifact even when the lock is missing). The drift-keep rule ("fragments that no longer match — a user re-resolved — are left alone") is about a block that still exists with different content. When a block is gone, rollback / vendor --revert / remove should drop the ledger entry and the artifact, with a warning, once the lock no longer mentions the uuid. Failing that, the printed remedy must actually work.
  • Actual: the artifact is kept forever, rollback / remove exit 1 forever, vendor --revert exits 0 having done nothing, and the remedy loops.

Matrix (Linux, main 045d7ec, Node 22)

yarn rollback after yarn remove scan --mode vendored + remove remedy stale artifact left
1.7.0 exit 1 (x2) exit 1 yes
1.22.22 exit 1 (x3, two separate projects) exit 1 yes

Hosted mode passes the same flow: after yarn remove, rollback succeeds, the remaining block is restored and .socket/ is removed. Not OS-specific: the decision is made on lock text, so no probe run.

Suspect code

  • crates/socket-patch-core/src/vendor/yarn_classic_lock.rs:607: revert_recorded_block turns a missing block into vendor_lock_entry_drifted, so outcome.drift_skipped() (~505) returns early with keep_artifact before the lock_text_mentions_uuid probe gets a chance to show the artifact is unreferenced.
  • npm_lock.rs:1121, bun_lock.rs:1005 and pnpm_lock.rs:3112/3191/3255 emit the same "no longer exists; nothing to restore" drift, so npm uninstall / bun remove / pnpm remove are probably affected the same way (not verified here).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions