Skip to content

Commit 11934b9

Browse files
committed
Keep unwired-entry advice to a safe command
The previous wording told users to restore the pre-vendor lockfile and prune again. That lock has no .socket/vendor paths, so the prune would revert every vendored entry, not just the stale ones. The warning now names only scan --prune. A prune that drift-keeps an entry already explains it on its own GC: kept line, so the warning points there instead of suggesting a lock edit whose reach it cannot bound. Assisted-by: Claude Code:claude-opus-5-5
1 parent beb2118 commit 11934b9

3 files changed

Lines changed: 16 additions & 56 deletions

File tree

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified
11491149
| `reinstall_required` | rollback `warnings[]` | rollback (v5.0): vendored/hosted wiring was unwound, but installed trees keep their patched bytes until the next package-manager install — the stale-install advisory. |
11501150
| `hosted_state_not_preservable` | rollback `warnings[]` | rollback `--preserve-state` (v5.0): hosted pins were restored to upstream anyway — the lockfile pins are hosted mode's only record, so there is no local state to preserve; re-run `scan --mode hosted` to re-wire. (`remove --preserve-state` prints the same note on stderr.) |
11511151
| `out_of_scope_copies_restored` | rollback `warnings[]` | path-scoped rollback (v5.0): a selected patch had installed copies outside the given patterns; ALL copies were restored (patches are per-package). Informational — never flips the exit. |
1152-
| `vendor_ledger_entry_unwired` | scan `warnings[]` | a vendored entry's dependency left the lockfile (upgraded or removed), so the ledger supplement skipped it; the detail names the purls and points at `scan --prune`, which reverts them (no warning on a pruning non-hosted run). For an entry that prune drift-keeps because its lock entries were re-resolved since vendoring, it names the convergent way out: restore the lockfile from before vendoring, `scan --prune` again (the reverts then converge), and re-run the package manager's install. |
1152+
| `vendor_ledger_entry_unwired` | scan `warnings[]` | a vendored entry's dependency left the lockfile (upgraded or removed), so the ledger supplement skipped it; the detail names the purls and points at `scan --prune`, which reverts them (no warning on a pruning non-hosted run). An entry that prune drift-keeps (its lock entries were re-resolved since vendoring, e.g. an npm uninstall re-locked it away) is reported on the prune's `GC: kept` line and keeps being warned about. |
11531153
| `path_scope_excluded_supplements` | scan `warnings[]` | path-scoped scan (v5.0): lockfile-only / vendor-ledger supplement packages have no installed path and were excluded from the scoped scan; the detail carries the count. |
11541154
| `vendor_commit_failed` | top-level error (`vendor`, and the nested vendor envelope of `scan` / `get --mode vendored`) | v5.0 group commit: the run's lockfile / manifest / ledger edits could not be written (the detail names the I/O error). Exit 1; the project's lockfiles and `.socket/vendor/state.json` are left as they were before the run (a partially-applied commit is put back), and the per-package events describe the uncommitted outcome. When putting a partially-applied commit back fails too, the journal is kept instead and the detail says the next socket-patch command in the project finishes the commit. |
11551155
| `vendor_state_unreadable` | rollback `warnings[]`; remove top-level error | corrupt-ledger containment (v5.0). Rollback: an unreadable vendor ledger skips the vendored leg + manifest cleanup + GC and drives `partial_failure` exit 1 while the agent and hosted legs still run. Remove: a hard top-level error before any mutation. Also the Bun vendored preflight's refusal code: `get` / `scan --mode vendored`, `vendor`'s pre-takeover check and the `--dry-run` `would_refuse` preview report an unreadable `.socket/vendor/state.json` as itself (`errorCode` in `patches[]` / `download.patches[]`, or `get <uuid>`'s top-level `error.code`), fail-closed — nothing is exempt — instead of a Bun lock code. (v4's `redirect_state_unreadable` is no longer emitted: v5 never reads the redirect ledger on these paths.) |

‎crates/socket-patch-cli/src/commands/scan/render.rs‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,19 +225,18 @@ pub(super) const PRUNE_SKIPPED_EMPTY: &str = "Warning: --prune skipped: no insta
225225

226226
/// The `vendor_ledger_entry_unwired` warning: vendored entries whose
227227
/// dependency left the lockfile (upgraded or removed), so the scan no
228-
/// longer discovers them. `--prune` reverts them, unless their lock entries
229-
/// were re-resolved since vendoring: the GC then drift-keeps them, and only
230-
/// restoring the pre-vendor lock lets the same prune converge (the
231-
/// drift-keep contract), so the detail names that way out too.
228+
/// longer discovers them, and `--prune` reverts them. A prune that
229+
/// drift-keeps an entry (its lock entries were re-resolved since vendoring)
230+
/// explains that on its own `GC: kept` line, so the detail points there
231+
/// rather than suggesting a lock edit whose reach it cannot bound.
232232
pub(super) fn unwired_vendored_detail(purls: &[String]) -> String {
233233
let one = purls.len() == 1;
234234
let them = if one { "it" } else { "them" };
235235
format!(
236236
"{} no longer used by the lockfile (the dependency was upgraded or removed) and {} \
237-
skipped ({}); run `socket-patch scan --prune` to revert {them}. If that run keeps \
238-
{them} because the lock entries were re-resolved since vendoring, restore the lockfile \
239-
from before vendoring, run `socket-patch scan --prune` again, then re-run your package \
240-
manager's install",
237+
skipped ({}); run `socket-patch scan --prune` to revert {them} (a prune that keeps \
238+
{them} because the lock entries were re-resolved since vendoring says so on its \
239+
`GC: kept` line)",
241240
crate::ui::plural(purls.len(), "vendored entry is", "vendored entries are"),
242241
if one { "was" } else { "were" },
243242
purls.join(", "),

‎crates/socket-patch-cli/tests/scan_vendor_e2e.rs‎

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,6 @@ async fn scan_vendor_prune_reconciles_unwired_entry_on_an_empty_crawl() {
12541254
mount_patch_api(&mock, UUID).await;
12551255
let tmp = tempfile::tempdir().unwrap();
12561256
write_fixture(tmp.path());
1257-
let original_lock = std::fs::read(tmp.path().join("package-lock.json")).unwrap();
12581257
let (code, stdout, stderr) = run_scan_vendor(tmp.path(), &mock.uri(), &[]);
12591258
assert_eq!(code, 0, "stdout={stdout}; stderr={stderr}");
12601259

@@ -1268,8 +1267,11 @@ async fn scan_vendor_prune_reconciles_unwired_entry_on_an_empty_crawl() {
12681267
"": { "name": "scan-vendor-test", "version": "0.0.0" }
12691268
}
12701269
});
1271-
let uninstalled_lock = serde_json::to_vec_pretty(&lock).unwrap();
1272-
std::fs::write(tmp.path().join("package-lock.json"), &uninstalled_lock).unwrap();
1270+
std::fs::write(
1271+
tmp.path().join("package-lock.json"),
1272+
serde_json::to_vec_pretty(&lock).unwrap(),
1273+
)
1274+
.unwrap();
12731275
std::fs::remove_dir_all(tmp.path().join("node_modules/left-pad")).unwrap();
12741276
let unwired = |v: &serde_json::Value| {
12751277
v["warnings"]
@@ -1301,8 +1303,8 @@ async fn scan_vendor_prune_reconciles_unwired_entry_on_an_empty_crawl() {
13011303
);
13021304

13031305
// The drift-kept entry is still unwired, so the next plain rescan warns
1304-
// again, and its detail names the way out a drift-keep needs: `--prune`
1305-
// alone cannot clear it.
1306+
// again; its detail names the purl and the prune's `GC: kept` report
1307+
// instead of a lock edit that could unwire other vendored entries.
13061308
let (code, stdout, stderr) = run_scan_vendor(tmp.path(), &mock.uri(), &[]);
13071309
assert_eq!(code, 0, "stdout={stdout}; stderr={stderr}");
13081310
let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON");
@@ -1315,50 +1317,9 @@ async fn scan_vendor_prune_reconciles_unwired_entry_on_an_empty_crawl() {
13151317
.unwrap_or_else(|| panic!("envelope={v}"))
13161318
.to_string();
13171319
assert!(
1318-
detail.contains(&format!("({PURL})")) && detail.contains("from before vendoring"),
1320+
detail.contains(&format!("({PURL})")) && detail.contains("`GC: kept`"),
13191321
"{detail}"
13201322
);
1321-
1322-
// Following that advice converges: restore the pre-vendor lock, prune
1323-
// (report mode, so the restored dependency is not re-vendored), then
1324-
// the package manager's install drops the dependency again. No entry,
1325-
// no artifact, no warning.
1326-
std::fs::write(tmp.path().join("package-lock.json"), &original_lock).unwrap();
1327-
let out = Command::new(binary())
1328-
.args([
1329-
"scan",
1330-
"--json",
1331-
"--prune",
1332-
"--yes",
1333-
"--api-url",
1334-
&mock.uri(),
1335-
"--api-token",
1336-
"fake-token",
1337-
"--org",
1338-
ORG_SLUG,
1339-
])
1340-
.current_dir(tmp.path())
1341-
.output()
1342-
.expect("run");
1343-
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
1344-
assert_eq!(out.status.code(), Some(0), "stdout={stdout}");
1345-
let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON");
1346-
assert_eq!(
1347-
v["gc"]["revertedVendoredEntries"],
1348-
serde_json::json!([PURL]),
1349-
"envelope={v}"
1350-
);
1351-
std::fs::write(tmp.path().join("package-lock.json"), &uninstalled_lock).unwrap();
1352-
assert!(
1353-
!tmp.path()
1354-
.join(format!(".socket/vendor/npm/{UUID}"))
1355-
.exists(),
1356-
"artifact dir removed"
1357-
);
1358-
let (code, stdout, stderr) = run_scan_vendor(tmp.path(), &mock.uri(), &[]);
1359-
assert_eq!(code, 0, "stdout={stdout}; stderr={stderr}");
1360-
let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON");
1361-
assert_eq!(unwired(&v), 0, "converged: {v}");
13621323
}
13631324

13641325
/// Interactive (non-JSON) `scan --vendor` pre-verifies patch baselines:

0 commit comments

Comments
 (0)