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

  1. added a commit that references this issue on Oct 2, 2026
  2. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1 (vlt is in the npm family). It isn't a duplicate, and I found no open or merged PR that fixes it.

    On main 61cfb9b, vendored_ledger_supplement (crates/socket-patch-cli/src/commands/scan/discovery.rs:141) adds back every ledger entry that has no crawled counterpart. It never checks whether the lock still wires that entry (the prune GC does this check through dispatch_in_use_one). The issue says the npm package-lock path hits the same code. A fix belongs in the shared supplement, so it covers every ecosystem and not just vlt.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue. Shared root cause: the vendored ledger supplement re-adds entries the lock no longer wires. Branch: agent/fix-vendored-supplement-unwired. Claim-ID: 2026-10-02T08:21:03Z-2b345a


    Generated by Claude Code

  4. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #543


    Generated by Claude Code

  5. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Scope note for #543. That PR fixes the shared ledger-supplement cause: vlt rescans and --prune exit 0 after a bump or uninstall, and the npm package-lock rescan exits 0 too. It doesn't change the npm prune behavior this issue mentions (revertedVendoredEntries: []). When an npm uninstall re-locks the vendored entry away, the GC drift-keeps it on purpose (scan_prune_reverts_unused_vendored_entry asserts this). That entry then keeps getting the new vendor_ledger_entry_unwired warning, and neither vendor --revert nor remove <purl> clears it per package. Settling whether an entry whose lock entry vanished (with nothing left referencing its artifact) should be reclaimed is the separate npm follow-up.


    Generated by Claude Code

  6. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] vlt bug-hunt routine (ledger #307): I checked draft PR #543 (head 11934b9) against real vlt installs on Linux, with a local mock registry and patch server.

    vlt case main 61cfb9b: rescan / --prune / rescan #543: rescan / --prune / rescan
    1.3.5 vlt install left-pad@1.2.0 after vendoring 1 / 1 / 0 (vendor_lock_entry_not_found) 0 / 0 / 0 (vendor_ledger_entry_unwired warning)
    1.3.5 vlt uninstall left-pad after vendoring 1 / 1 / 0 0 / 0 / 0
    1.2.0 bump 1 / 1 / 0 0 / 0 / 0
    1.2.0 uninstall 1 / 1 / 0 0 / 0 / 0

    In every cell, --prune empties .socket/vendor/npm, and the installed package stays on the version the user chose (older after the bump, and absent after the uninstall). On vlt, #543 fixes the exit codes this issue reports. I haven't tested macOS or Windows yet.


    Generated by Claude Code

  7. added a commit that references this issue on Oct 2, 2026
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