Skip to content

Vendored vlt scan exits 1 after the patched dependency is upgraded or uninstalled, and even scan --prune exits 1 while it reverts the stale entry #541

Description

[agent] Found by the scheduled vlt bug-hunt routine (ledger #307).

Summary

Say a vlt project vendors left-pad@1.3.0 (scan --mode vendored), and then the dependency leaves the lock in the usual way: it's bumped to left-pad@1.2.0 and vlt install runs, or vlt uninstall left-pad runs. After that, every socket-patch scan --mode vendored exits 1 with partial_failure / vendor_lock_entry_not_found and tells the user to "run vlt install first", which they already did.

The documented way out is scan --prune. It does revert the stale ledger entry (gc.revertedVendoredEntries: ["pkg:npm/left-pad@1.3.0"], and .socket/ is gone afterwards), but the same run still exits 1. It reports the purl it just reconciled as a failed vendor. Only the run after that one exits 0.

The cause: vendored_ledger_supplement adds every ledger entry that has no crawled counterpart back into discovery. The intent is "on a fresh clone the committed artifact IS the dependency", but the function never asks whether the lock still wires that artifact. The vendor step then tries to re-vendor a package the lock no longer has. It does this before the prune GC runs, which is the documented order.

Impact

  • A scheduled scan --mode vendored (a cron or CI job) goes red permanently after any routine dependency upgrade or removal. The error points users at the wrong fix (vlt install).
  • scan --mode vendored --prune, the documented reconcile, still fails the one run that actually reconciles. CI can't tell a real vendor failure from a successful cleanup.
  • The human output is misleading too. It prints installed content differs from patch baseline; the patched content will be vendored for left-pad@1.3.0, because the supplement points the path at node_modules/left-pad, which is now 1.2.0.

Repro (Linux, vlt 1.3.5, main 61cfb9b)

The local mock registry and patch API are the ones described in the ledger (#307). Any registry with left-pad@1.3.0 and @1.2.0 plus a patch for 1.3.0 works.

mkdir app && cd app
echo '{"config":{"registries":{"npm":"http://127.0.0.1:18555/"}}}' > vlt.json
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
vlt install --allow-scripts ':scripts'
socket-patch scan --mode vendored --yes          # rc 0, vendored
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.2.0"}}' > package.json
vlt install --allow-scripts ':scripts'           # lock now has only ~npm~left-pad@1.2.0
socket-patch scan --mode vendored --yes          # rc 1  partial_failure vendor_lock_entry_not_found
socket-patch scan --mode vendored --prune --yes  # rc 1  ...but gc.revertedVendoredEntries=[left-pad@1.3.0]
socket-patch scan --mode vendored --yes          # rc 0

Human output of the --prune run:

  pkg:npm/left-pad@1.3.0: installed content differs from patch baseline; the patched content will be vendored
Downloading 1 patch...
  [error] pkg:npm/left-pad@1.3.0 (vendor_lock_entry_not_found): vlt-lock.json has no default-registry entry for left-pad@1.3.0; run `vlt install` first
Nothing was vendored: 1 patch failed (see above).
GC: reverted 1 vendored entry; swept 0 orphan vendor dirs.
rc 1

The vlt uninstall left-pad variant behaves the same way (rescan rc 1, prune rc 1 and reverts, next run rc 0).

Expected vs actual

  • CLI_CONTRACT.md (scan → vendored): "scan --prune reconciles ledger entries whose dependency left the lockfile", and the prune's leg (b) reverts "EVERY ledger entry whose dependency is no longer in the lockfile graph". A run that does exactly that should exit 0, perhaps with a warning. It shouldn't report partial_failure for the entry it reconciled.
  • The ledger supplement is documented for the fresh-clone case, where the committed artifact satisfies the lock. When the lock no longer references the artifact, the entry isn't a discoverable dependency. A plain rescan should skip it with a warning (pointing at --prune) rather than fail with advice to run vlt install.
  • Actual: plain rescans fail with exit 1 until --prune runs, and the --prune run itself fails with exit 1.

Matrix (Linux; macOS / Windows untested because probe branches are blocked this run)

vlt bump → rescan bump → --prune prune reverts entry next run
1.0.10 rc 1 rc 1 yes rc 0
1.2.0 rc 1 rc 1 yes rc 0
1.3.3 rc 1 rc 1 yes rc 0
1.3.5 rc 1 (×2) rc 1 (×2) yes rc 0
1.3.5, vlt uninstall rc 1 rc 1 yes rc 0

npm control on the same mock (package-lock v3): the bump → rescan also exits 1. --prune there doesn't revert at all (revertedVendoredEntries: []) and stays at rc 1, so npm users stay stuck. I've passed that to the npm routine's ledger separately. The supplement/ordering part is shared code.

First bad release: none. Release 4.0.0 predates vendored vlt support, so this is unreleased main behaviour.

Suspect code

  • crates/socket-patch-cli/src/commands/scan/discovery.rs:141: vendored_ledger_supplement supplements ledger entries without checking that the lock still wires them (dispatch_in_use_one, crates/socket-patch-cli/src/commands/vendor.rs:260, already answers Some(false) here; the prune GC uses it).
  • crates/socket-patch-cli/src/commands/scan/mod.rs:1626: the call site. The vendor step runs before the prune GC.
  • crates/socket-patch-core/src/vendor/vlt_lock.rs:475: the refusal and its misleading hint.

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions