Skip to content

Vendored cargo scan exits 1 permanently after a vendored crate is removed or bumped, even with --prune, because the shared registry cache still holds the old version #573

Description

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

Summary

#541 / #543 made a vendored rescan exit 0 with a vendor_ledger_entry_unwired warning after the vendored dependency is upgraded or removed, and made scan --prune revert the entry and exit 0. On cargo that fix doesn't take effect in the normal case.

After cfg-if is removed from Cargo.toml (or bumped to another version) and cargo re-locks, cfg-if-1.0.5/ is still extracted under the shared $CARGO_HOME/registry/src/ (cargo never deletes it). The cargo crawler's local-mode fallback crawls the whole registry cache, so discovery still finds pkg:cargo/cfg-if@1.0.5. The patch API offers its patch, and the lock-text pre-check refuses it:

  • uninstall: locked_version_mismatch: "cfg-if is not present in Cargo.lock (patch targets 1.0.5)"
  • bump: locked_version_mismatch: "Cargo.lock resolves cfg-if to 1.0.0 but the patch targets 1.0.5"

The results:

  • The plain rescan exits 1 (partial_failure) with no vendor_ledger_entry_unwired warning.
  • scan --mode vendored --prune reverts the entry (gc.revertedVendoredEntries: ["pkg:cargo/cfg-if@1.0.5"]) but still exits 1.
  • Every later vendored rescan also exits 1, indefinitely. The stale copy stays in the cache until cargo clean/cache GC deletes it, and that's per machine, not per project.
  • The same failure hits a sibling project that never vendored anything and doesn't depend on cfg-if, as long as it shares the CARGO_HOME: scan --mode vendored exits 1 with the same locked_version_mismatch for a crate that isn't in its Cargo.lock, and --dry-run previews would_vendor for it.

With a fresh CARGO_HOME that holds only the new lock's crates, the same project behaves as the contract says (exit 0, vendor_ledger_entry_unwired). So the crawl of the shared cache is the trigger.

Impact

  • On a developer machine or a CI runner with a cached ~/.cargo, a scheduled scan --mode vendored goes red permanently after any routine removal or bump of a vendored crate. The documented reconcile (--prune) doesn't clear it.
  • Any cargo project on a machine where some other project pulled a patchable crate version fails vendored scans for a crate it doesn't use. Exit 1 and partial_failure can't be told apart from a real vendoring failure.

Repro (Linux, main 1169ae6)

This was reproduced with a real cargo build in a local (uncommitted) test in crates/socket-patch-cli/tests/e2e_vendor_cargo_build.rs, using the same wiremock patch API, view and prebuilt vendor service as cargo_get_uuid_vendored_fresh_checkout_locked_build. The batch API answers only for the requested purls, as production does. Shape:

cargo new consumer && cd consumer
printf 'cfg-if = "1.0"\nitoa = "1"\n' >> Cargo.toml
cargo build                                   # locks cfg-if 1.0.5, extracts it into $CARGO_HOME/registry/src
socket-patch scan --mode vendored --yes       # rc 0, cfg-if vendored; `cargo run --locked --offline` links the patched copy
sed -i '/^cfg-if/d' Cargo.toml                # or: cfg-if = "=1.0.0"
cargo build                                   # re-lock: cfg-if gone (lock gets [[patch.unused]] for the Socket copy)
socket-patch scan --mode vendored --yes          ; echo $?   # 1  locked_version_mismatch, no unwired warning
socket-patch scan --mode vendored --yes --prune  ; echo $?   # 1  (and gc.revertedVendoredEntries = [cfg-if@1.0.5])
socket-patch scan --mode vendored --yes          ; echo $?   # 1  forever after

# sibling project, same CARGO_HOME, never used cfg-if:
cargo new sibling && cd sibling && echo 'itoa = "1"' >> Cargo.toml && cargo build
socket-patch scan --mode vendored --yes          ; echo $?   # 1  "`cfg-if` is not present in Cargo.lock"

The post-prune download.patches[]:

{"purl":"pkg:cargo/cfg-if@1.0.5","action":"failed","errorCode":"locked_version_mismatch","error":"`cfg-if` is not present in Cargo.lock (patch targets 1.0.5)"}

Expected vs actual

  • Expected (CLI_CONTRACT.md, scan --vendor): "an entry the lockfile in-use probe … proves unwired, because the dependency was upgraded or removed, is NOT discovered and so is never re-vendored. A run without a non-hosted --prune reports it through the run-level vendor_ledger_entry_unwired warning; a --prune run reverts it in its GC and exits 0." The same section also says: "A package absent from the lock and not installed never reached its backend … the vendor loop skips it".
  • Actual: the ledger supplement is filtered correctly, but the crawler re-discovers the same purl from the shared cache. The rescan and the prune both exit 1, and so does every run after them, in this project and in unrelated ones.

Note: cargo_refuses_early_only_an_installed_crate (tests/scan_vendor_e2e.rs) deliberately pins failed / locked_version_mismatch for a crate cached at a version other than the locked one, with the crate still in the lock. The uninstall and sibling cases (crate not in Cargo.lock at all) aren't pinned. In a lockfile-backed cargo project, a registry-cache crate the lock doesn't resolve probably shouldn't count as "installed" for vendored discovery. That's a maintainer call; the bump case may need the same answer.

OS × version

OS cargo uninstall → rescan / --prune / later rescans bump (=1.0.0) sibling project clean CARGO_HOME
Linux 1.84.1 exit 1 / 1 / 1 — exit 1 —
Linux 1.93.1 exit 1 / 1 / 1 (3/3 runs) exit 1 / 1 exit 1 exit 0 + vendor_ledger_entry_unwired (pass)
Linux 1.97.0 (stable) exit 1 / 1 / 1 — exit 1 —
macOS / Windows — untested (no probe this run; the code path is OS-independent)

Not bisected. The crawl fallback predates #543, and #543's fix (the ledger-supplement in-use filter) doesn't reach this path.

Suspect code

  • crates/socket-patch-core/src/crawlers/cargo_crawler.rs:144-184 (get_crate_source_paths: local mode falls back to the whole $CARGO_HOME/registry/src/ with no Cargo.lock filter)
  • crates/socket-patch-core/src/vendor/cargo.rs:492-512 (the locked_version_mismatch refusal that the lock-text pre-check surfaces as a run failure)
  • crates/socket-patch-cli/src/commands/scan/discovery.rs (vendored_ledger_supplement, Fix vendored rescans failing on unwired ledger entries (#541) #543: filters the ledger but not the crawl)

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