GeezerVison is showing github.com — fix(scan): warn when a mode takeover leaves the other ledger stale by mikolalysenko · Pull Request #164 · SocketDev/socket-patch
Conversation
Problem
Switching a project's patch mode leaves the DISPLACED mode's ledger
stale, with no warning or reconciliation.
- Hosted mode writes
.socket/vendor/redirect-state.json.
- Vendored mode writes
.socket/vendor/state.json (+ committed tarballs
under .socket/vendor/<eco>/).
Running scan --mode vendored on a project already redirected by hosted (and
vice-versa) rewires the lockfile to the new mode, but the OLD mode's ledger
stays on disk asserting wiring that is no longer live — and, vendored→hosted,
the orphaned vendored tarball is left behind. Both scans report
warnings: []. Anything auditing a ledger as "what is live" (including vex)
is misled.
Confirmed both directions against real prod ([email protected] npm): after
hosted→vendored, package-lock.json resolves minimist to
file:.socket/vendor/npm/.../minimist-1.2.2.tgz while redirect-state.json
still says mode: hosted, edits[0].action: rewritten, new.resolved: https://patch.socket.dev/...; mirror for vendored→hosted. No warning either
way. (Kills sweep finding: stale-ledger-on-mode-takeover, P2; reduces the
downstream vex-prefers-stale-vendor-ledger issue.)
Fix
Detect the cross-mode overlap and warn in each flow — no silent mutation of
the other mode's ledger.
crates/socket-patch-cli/src/commands/scan/mod.rs:421 — new
overlapping_ledger_purls(cwd) loads BOTH ledgers and returns the PURLs
claimed by each (canonicalized, mirroring vendored_ledger_supplement).
A non-empty result means one mode took the lockfile over from the other for
those package(s) and exactly one ledger is stale per PURL. Empty when either
ledger is missing/empty or the two describe disjoint packages (no false
positives). Plus mode_takeover_detail(...) and the two warning codes
REDIRECT_SUPERSEDES_VENDORED / VENDOR_SUPERSEDES_REDIRECT.
crates/socket-patch-cli/src/commands/scan/hosted.rs:469 — run_redirect
detects a still-live vendored ledger after writing its own ledger and adds a
redirect_supersedes_vendored warning to the JSON warnings[] (line 524)
and stderr (line 587).
crates/socket-patch-cli/src/commands/scan/vendor_flow.rs:56 — new
note_vendor_supersedes_redirect(...) (mirrors note_classic_migration_risk)
pushes a vendor_supersedes_redirect RunWarning onto the envelope +
stderr; called at both finalize points of run_scan_vendor_step
(lines 139 and 178).
The warning names the displaced package(s) and the stale ledger / orphaned
artifacts to clean up.
Test
crates/socket-patch-cli/src/commands/scan/mod.rs (tests module), hermetic,
no network:
overlapping_ledgers_flag_the_taken_over_package — both ledgers claim
minimist ⇒ detection returns exactly that PURL (the takeover warning fires).
single_ledger_present_flags_nothing — a first-time redirect (only one
ledger, or none) displaces nothing ⇒ empty (guards against warning on the
FIRST scan of a fresh project).
disjoint_ledgers_are_not_a_takeover — one package redirected, a different
one vendored ⇒ empty (no false positive on a legitimate split).
takeover_detail_names_direction_package_and_remediation — each direction's
detail names the PURL, the correct stale ledger file, and distinct codes.
cargo test -p socket-patch-cli --lib 'commands::scan::tests' → 7 passed.
cargo build -p socket-patch-cli and cargo clippy -p socket-patch-cli clean.
Scope
- Detection + warning only. Full auto-reconciliation (marking the displaced
ledger superseded, or deleting the now-orphaned vendored tarball) is
deliberately deferred — the fix instruction called for warning as sufficient,
and neither mode should silently mutate/delete the other's ledger. The
warning text points the user at vendor --revert / removing
redirect-state.json / deleting orphaned .socket/vendor/<eco>/.
- Detection is disk-state-based (both ledgers on disk with overlapping PURLs),
so it fires on the SECOND scan (the takeover) and stays silent on the first,
matching the real prod repro in both directions.
- Hosted
--dry-run does not write redirect-state.json, so a would-be
takeover during a pristine dry run is not flagged; the real (non-dry-run)
takeover is. Not addressed here.
- No cross-dependency on other in-flight PRs.
🤖 Generated with Claude Code
Note Medium Risk
Changes audit-facing warnings and CLI JSON contracts for scan modes; behavior is advisory only (no ledger mutation), but misleading VEX/audit reads were the reported production issue.
Overview
When hosted and vendored patch modes both have ledgers claiming the same package, the lockfile only reflects the current mode — the other ledger (and vendored tarballs after hosted takeover) can stay on disk with no signal. This PR detects overlapping PURLs between redirect-state.json and state.json (canonicalized like existing vendor supplement logic) and warns without deleting or rewriting the displaced ledger.
Hosted run_redirect adds redirect_supersedes_vendored to JSON warnings[] and stderr after its ledger write. Vendored scan flow adds note_vendor_supersedes_redirect (envelope warnings[] + stderr), aligned with note_classic_migration_risk, at both finalize paths in run_scan_vendor_step. Warning text names packages, which file is stale, and manual cleanup (vendor --revert, remove redirect ledger, etc.).
Hermetic tests cover overlap detection, single/disjoint ledgers (no false positives), and direction-specific detail strings.
Reviewed by Cursor Bugbot for commit 44df961. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
left a comment
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Wrong takeover warning direction
- Fixed by checking that packages were actually confirmed/vendored in the current run before emitting takeover warnings, preventing false warnings on dry-runs and no-op scans where the lockfile still points to the other mode.

Or push these changes by commenting:
Preview (b56c9b00d2)
diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs
--- a/crates/socket-patch-cli/src/commands/scan/hosted.rs
+++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs
@@ -472,13 +472,28 @@
// stale. Detect + warn (JSON `warnings[]` and stderr) WITHOUT deleting the
// other mode's ledger; full reconciliation is deferred (see PR Scope).
// Read after the ledger write above so a non-dry-run reflects this run.
+ // Only warn about PURLs that THIS RUN confirmed redirected (landed in the
+ // lockfile) — a dry-run or no-op scan that redirected zero packages must
+ // not warn, even when both ledgers exist (the lockfile still points at the
+ // vendored artifacts, not the hosted server).
let mut takeover_warnings: Vec<serde_json::Value> = Vec::new();
- let superseded = super::overlapping_ledger_purls(&args.common.cwd).await;
- if !superseded.is_empty() {
- takeover_warnings.push(serde_json::json!({
- "code": super::REDIRECT_SUPERSEDES_VENDORED,
- "detail": super::mode_takeover_detail(&superseded, /*current_is_hosted=*/ true),
- }));
+ if !confirmed.is_empty() {
+ use socket_patch_core::utils::purl::{normalize_purl, strip_purl_qualifiers};
+ let confirmed_purls: std::collections::HashSet<String> = confirmed
+ .iter()
+ .map(|(purl, _)| normalize_purl(strip_purl_qualifiers(purl)).into_owned())
+ .collect();
+ let all_overlapping = super::overlapping_ledger_purls(&args.common.cwd).await;
+ let superseded: Vec<String> = all_overlapping
+ .into_iter()
+ .filter(|purl| confirmed_purls.contains(purl))
+ .collect();
+ if !superseded.is_empty() {
+ takeover_warnings.push(serde_json::json!({
+ "code": super::REDIRECT_SUPERSEDES_VENDORED,
+ "detail": super::mode_takeover_detail(&superseded, /*current_is_hosted=*/ true),
+ }));
+ }
}
// Emit an OpenVEX attestation when `--vex` was requested. The redirected
diff --git a/crates/socket-patch-cli/src/commands/scan/vendor_flow.rs b/crates/socket-patch-cli/src/commands/scan/vendor_flow.rs
--- a/crates/socket-patch-cli/src/commands/scan/vendor_flow.rs
+++ b/crates/socket-patch-cli/src/commands/scan/vendor_flow.rs
@@ -61,8 +61,32 @@
/// at the envelope level (JSON `warnings[]` and stderr), mirroring
/// [`note_classic_migration_risk`]; the stale ledger is NOT deleted here
/// (reconciliation is deferred — see the redirect twin in `hosted.rs`).
+/// Only warns about PURLs that THIS RUN actually vendored (have Applied or
+/// Rebuilt events) — a no-op vendor run or dry-run that vendored zero packages
+/// must not warn, even when both ledgers exist.
async fn note_vendor_supersedes_redirect(env: &mut Envelope, cwd: &Path, common: &GlobalArgs) {
- let superseded = super::overlapping_ledger_purls(cwd).await;
+ use crate::json_envelope::PatchAction;
+ use socket_patch_core::utils::purl::{normalize_purl, strip_purl_qualifiers};
+
+ // Extract PURLs that were actually vendored in this run (Applied or Rebuilt).
+ let vendored_purls: std::collections::HashSet<String> = env
+ .events
+ .iter()
+ .filter(|e| matches!(e.action, PatchAction::Applied | PatchAction::Rebuilt))
+ .filter_map(|e| e.purl.as_ref())
+ .map(|purl| normalize_purl(strip_purl_qualifiers(purl)).into_owned())
+ .collect();
+
+ if vendored_purls.is_empty() {
+ return;
+ }
+
+ let all_overlapping = super::overlapping_ledger_purls(cwd).await;
+ let superseded: Vec<String> = all_overlapping
+ .into_iter()
+ .filter(|purl| vendored_purls.contains(purl))
+ .collect();
+
if superseded.is_empty() {
return;
}
You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 44df961. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Switching a project's patch mode rewired the lockfile to the new mode but
left the displaced mode's ledger on disk asserting wiring that is no longer
live (hosted: .socket/vendor/redirect-state.json; vendored:
.socket/vendor/state.json + orphaned tarballs). Both scans reported
`warnings: []`, so anything auditing a ledger as "what is live" (including
`vex`) was misled.
Detect the overlap (PURLs claimed by BOTH ledgers) and emit a takeover
warning in each flow — `redirect_supersedes_vendored` from the hosted flow,
`vendor_supersedes_redirect` from the vendored flow — surfaced in both the
JSON `warnings[]` and stderr, naming the displaced package(s) and the stale
ledger/orphaned artifacts to clean up. Neither mode silently mutates or
deletes the other's ledger; reconciliation is deferred.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The overlap between the hosted and vendored ledgers only proves both name
the same package(s) — not which mode won. Each flow assumed the running
command displaced the other, so a hosted dry-run/no-op (or a vendored run
that did not rewire the overlap) emitted the OPPOSITE `*_supersedes_*`
warning and pointed cleanup at the ledger matching the LIVE lockfile — it
could tell the user to delete the ledger for what is actually installed.
Decide direction from the actual current lockfile wiring instead.
`classify_overlap_takeover` reads the scan inventory (a `patch.socket.dev`
`resolved` ⇒ hosted is live) and the vendored ledger's wired lockfile(s)
(a live `.socket/vendor/<eco>/<uuid>` marker ⇒ vendored is live), then
buckets each overlapping PURL by the mode the lock actually proves. The
hosted flow warns only for the hosted-live subset, the vendored flow only
for the vendored-live subset, and a PURL the lock proves neither way stays
silent. Remediation now always points at the ledger that does NOT match
the live lockfile.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 14, 2026
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|