You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Cargo vendored → hosted takeover un-vendors a crate before hosted mode refuses its Cargo.toml spelling (dotted keys, single quotes, registry = "crates-io"), leaving it unpatched, while --dry-run previews redirected: 1 #1020
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
scan --mode hosted over a vendored cargo crate reverts the vendored wiring (the [patch.crates-io] entry, the detached lock entry, the ledger entry and the committed copy) and only then runs the hosted Cargo.toml rewriter. If the rewriter can't pin the dependency, the crate ends up in neither mode. That happens for any spelling the cargo rewriter refuses with redirect_cargo_toml_dep_unrewritable:
Vendored mode accepts all three spellings, because it doesn't edit the dependency line. The refusal can be predicted from Cargo.toml alone, before anything is reverted. A plain hosted --dry-run on a never-vendored project with the same spelling already reports it. But the cargo branch of the takeover pre-check returns None, so the revert always runs first.
--dry-run doesn't model this either. It previews redirect_would_revert_vendored, redirected: 1 and exit 0, and the wet run then exits 1.
This is the cargo counterpart of #945 (Poetry), #853 (pnpm) and #944 (uv). #963 fixed the opposite direction (hosted → vendored) for every ecosystem, and I confirmed that fix holds for cargo (see below).
Impact
A project that was vendored and patched, and that builds the patched crate under --locked --offline, goes back to building the unpatched crates.io release after one scan --mode hosted. The run is loud (exit 1, partial_failure, redirect_takeover_unpatched), but by then the vendored copy, ledger entry and wiring are deleted. The user has to restore them from git or re-run scan --mode vendored. A CI job that previews with --dry-run sees a clean takeover.
Repro
This used a scratch copy of crates/socket-patch-cli/tests/mode_migration_cargo.rs (the same fixture, wiremock hosted registry and crates-index mocks). The only change was the root Cargo.toml dependency line, plus a main.rs that calls the patch's marker cfg_if::socket_patched() as a compile oracle.
# 1. vendored (offline), with Cargo.toml:
[dependencies]
cfg-if.version = "1.0" # or: cfg-if = '1.0' / cfg-if = { version = "1.0", registry = "crates-io" }
socket-patch vendor --json --offline # exit 0
cargo build --locked --offline # ok: links the patched copy (marker resolves)
# 2. preview the hosted takeover
socket-patch scan --mode hosted --dry-run --json --yes --api-url <mock> --org test-org --api-token fake
# exit 0, status "success", redirect.redirected = 1,
# warnings: [redirect_would_revert_vendored] <- no hint of the refusal
# 3. wet run
socket-patch scan --mode hosted --json --yes --api-url <mock> --org test-org --api-token fake
# exit 1, status "partial_failure", redirect.redirected = 0, warnings:
# redirect_cargo_toml_dep_unrewritable "cfg-if in Cargo.toml cannot be pinned (declared with dotted keys this rewriter does not edit); dependency skipped (nothing rewritten)"
# redirect_takeover_reverted_vendored
# redirect_takeover_unpatched "... the project now installs the UNPATCHED registry release ..."
# Cargo.toml: [patch.crates-io] gone; Cargo.lock: entry back on crates.io; vendor ledger: no entry
# 4. fresh checkout, empty CARGO_HOME
cargo build --locked
# error[E0425]: cannot find function `socket_patched` in crate `cfg_if` <- unpatched crates.io copy
Control on the same project with cfg-if = "1.0": the dry run and the wet run both report redirected: 1 / exit 0, and the fresh --locked build links the hosted patch.
Control on a never-vendored project with cfg-if.version = "1.0": the dry run and the wet run both report redirected: 0 with redirect_cargo_toml_dep_unrewritable. So the refusal is predictable without reverting anything.
Expected vs actual
Expected. The takeover should leave a package it can't pin still vendored. CLI_CONTRACT.md already does this for PyPI: a vendored requirements.txt package outside the hosted rewriter's reach "is refused BEFORE its revert, wet and --dry-run alike … Its wiring, ledger entry and wheel are kept, so it stays vendored and patched (exit 0)". The same contract says --dry-run "predicts the same refusal from the same signals instead of previewing redirect_would_revert_vendored". Keep the hosted pin when a vendored takeover is refused (#853, #944) #963 established the same rule for the opposite direction: a takeover the target mode doesn't carry through keeps the old mode's wiring.
Actual. For cargo, the dependency spelling is never checked before the revert. The crate is un-vendored, then skipped by the rewriter, and left unpatched. The dry run previews a clean takeover.
OS × version
OS
cargo
lock
dotted keys
single-quoted
registry = "crates-io"
plain "1.0" (control)
Linux
1.93.1
v4 (cargo default)
fail (×2)
fail
fail
pass
Linux
1.97.0 (stable)
v3
fail
fail
fail
pass
Linux
1.97.0 (stable)
v4
fail
fail
fail
pass
macOS / Windows
—
—
untested; the decision is pure socket-patch logic over Cargo.toml text, so no OS dependence is expected
Tested on main db83f01 (CLI 4.0.0, latest release v4.0.0). I didn't bisect this: the takeover-first ordering is how v5 hosted mode works.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:1887-1903 (takeover_refusal): Maven, PyPI and npm-family purls get a pre-revert refusal check, but every other purl, pkg:cargo/ included, returns None. A cargo arm could run the Cargo.toml rewriter's own dependency-spelling checks (the logic behind redirect_cargo_toml_dep_unrewritable) against the root manifest before dispatch_revert_one.
crates/socket-patch-cli/src/commands/scan/hosted.rs:1960-2005: the dry-run branch emits redirect_would_revert_vendored and pushes the purl into dry_run / migrated without consulting the rewriter. A pre-revert refusal check would fix the preview too.
[agent] Triage: priority:p2 (Cargo). Not a duplicate: it is the cargo counterpart of #945 (PyPI, PR #946), #853 and #944, but the fix lives in the cargo branch of the takeover pre-check, which currently returns None. Eligible for a fix.
[agent] Found by the scheduled Cargo bug-hunt routine (ledger #315).
Summary
scan --mode hostedover a vendored cargo crate reverts the vendored wiring (the[patch.crates-io]entry, the detached lock entry, the ledger entry and the committed copy) and only then runs the hosted Cargo.toml rewriter. If the rewriter can't pin the dependency, the crate ends up in neither mode. That happens for any spelling the cargo rewriter refuses withredirect_cargo_toml_dep_unrewritable:cfg-if.version = "1.0"cfg-if = '1.0'registry = "crates-io"(Hosted cargo redirect refuses a crates.io dependency declared with an explicitregistry = "crates-io", treating crates.io as "another registry" #386)Vendored mode accepts all three spellings, because it doesn't edit the dependency line. The refusal can be predicted from
Cargo.tomlalone, before anything is reverted. A plain hosted--dry-runon a never-vendored project with the same spelling already reports it. But the cargo branch of the takeover pre-check returnsNone, so the revert always runs first.--dry-rundoesn't model this either. It previewsredirect_would_revert_vendored,redirected: 1and exit 0, and the wet run then exits 1.This is the cargo counterpart of #945 (Poetry), #853 (pnpm) and #944 (uv). #963 fixed the opposite direction (hosted → vendored) for every ecosystem, and I confirmed that fix holds for cargo (see below).
Impact
A project that was vendored and patched, and that builds the patched crate under
--locked --offline, goes back to building the unpatched crates.io release after onescan --mode hosted. The run is loud (exit 1,partial_failure,redirect_takeover_unpatched), but by then the vendored copy, ledger entry and wiring are deleted. The user has to restore them from git or re-runscan --mode vendored. A CI job that previews with--dry-runsees a clean takeover.Repro
This used a scratch copy of
crates/socket-patch-cli/tests/mode_migration_cargo.rs(the same fixture, wiremock hosted registry and crates-index mocks). The only change was the rootCargo.tomldependency line, plus amain.rsthat calls the patch's markercfg_if::socket_patched()as a compile oracle.Control on the same project with
cfg-if = "1.0": the dry run and the wet run both reportredirected: 1/ exit 0, and the fresh--lockedbuild links the hosted patch.Control on a never-vendored project with
cfg-if.version = "1.0": the dry run and the wet run both reportredirected: 0withredirect_cargo_toml_dep_unrewritable. So the refusal is predictable without reverting anything.Expected vs actual
--dry-runalike … Its wiring, ledger entry and wheel are kept, so it stays vendored and patched (exit 0)". The same contract says--dry-run"predicts the same refusal from the same signals instead of previewingredirect_would_revert_vendored". Keep the hosted pin when a vendored takeover is refused (#853, #944) #963 established the same rule for the opposite direction: a takeover the target mode doesn't carry through keeps the old mode's wiring.OS × version
registry = "crates-io""1.0"(control)Cargo.tomltext, so no OS dependence is expectedTested on main
db83f01(CLI 4.0.0, latest release v4.0.0). I didn't bisect this: the takeover-first ordering is how v5 hosted mode works.Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:1887-1903(takeover_refusal): Maven, PyPI and npm-family purls get a pre-revert refusal check, but every other purl,pkg:cargo/included, returnsNone. A cargo arm could run the Cargo.toml rewriter's own dependency-spelling checks (the logic behindredirect_cargo_toml_dep_unrewritable) against the root manifest beforedispatch_revert_one.crates/socket-patch-cli/src/commands/scan/hosted.rs:1960-2005: the dry-run branch emitsredirect_would_revert_vendoredand pushes the purl intodry_run/migratedwithout consulting the rewriter. A pre-revert refusal check would fix the preview too.Related
registry = "crates-io", treating crates.io as "another registry" #386: a dependency withregistry = "crates-io"is refused by hosted mode. This issue is about what the takeover does with any refused spelling, not about the refusal itself.groupblock and then refuses to vendor it (gemfile_declaration_not_editable), so the project silently goes back to unpatched #775: the same class in other ecosystems.No probe runs: this is Linux-only evidence.