Skip to content

Commit 0e1be3f

Browse files
mikolalysenkoclaude
andcommitted
Print a refused hosted remove's error once (B77)
The hosted unwind leg prints each refusal as `Error: …` when it happens; remove then printed the same message again on its way out (plus "The manifest was not modified." on the manifest-backed path). A human run now gets the refusal once, followed by the manifest note where it applies. The --json envelope is unchanged and still carries the whole message. Regression test remove_hosted_revert_failure_prints_the_error_once covers the manifest-backed and hosted-only paths; it fails on the old rendering (the refusal appeared twice). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 32f05e1 commit 0e1be3f

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

‎crates/socket-patch-cli/src/commands/remove.rs‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -778,8 +778,7 @@ pub async fn run(args: RemoveArgs) -> i32 {
778778
leg
779779
}
780780
Err(err) => {
781-
let (code, msg) = hosted_unwind_error(err, true);
782-
emit_error_envelope(args.common.json, args.common.dry_run, code, msg);
781+
emit_hosted_unwind_error(&args.common, err, true);
783782
return 1;
784783
}
785784
};
@@ -1336,6 +1335,20 @@ fn hosted_unwind_error(err: HostedUnwindError, manifest_backed: bool) -> (&'stat
13361335
)
13371336
}
13381337

1338+
/// Report a stopped hosted unwind. JSON carries the whole message in the
1339+
/// error envelope. A human run already saw `Error: <why>` — the hosted leg
1340+
/// prints each refusal as it happens — so only the manifest-backed path's
1341+
/// extra fact is added; printing the refusal again would show the same
1342+
/// error twice (B77).
1343+
fn emit_hosted_unwind_error(common: &GlobalArgs, err: HostedUnwindError, manifest_backed: bool) {
1344+
let (code, msg) = hosted_unwind_error(err, manifest_backed);
1345+
if common.json {
1346+
emit_error_envelope(true, common.dry_run, code, msg);
1347+
} else if manifest_backed {
1348+
eprintln!("The manifest was not modified.");
1349+
}
1350+
}
1351+
13391352
/// Remove path for identifiers that match ONLY hosted lockfile pins (no
13401353
/// manifest entry, no vendor-ledger entry): confirm, restore each pin's
13411354
/// upstream registry entry, and report `Removed`/`hosted_reverted` events. Like the ledger-only vendored path,
@@ -1405,8 +1418,7 @@ async fn remove_hosted_only(
14051418
Ok(leg) => leg,
14061419
Err(err) => {
14071420
track_patch_remove_failed("hosted redirect revert failed", api_token, org_slug).await;
1408-
let (code, msg) = hosted_unwind_error(err, false);
1409-
emit_error_envelope(args.common.json, args.common.dry_run, code, msg);
1421+
emit_hosted_unwind_error(&args.common, err, false);
14101422
return 1;
14111423
}
14121424
};

‎crates/socket-patch-cli/tests/remove/covgap_commands_remove.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,40 @@ fn remove_hosted_only_revert_failure_fails_closed() {
10181018
assert_eq!(read_bytes(&lock_path), lock_before, "lock untouched");
10191019
}
10201020

1021+
/// B77: a human (non-JSON) hosted remove whose restore is refused prints
1022+
/// the refusal ONCE. The hosted leg prints each refusal as it happens;
1023+
/// remove used to print the same `Error:` line again on its way out.
1024+
/// Covers the manifest-backed path (which adds that the manifest was not
1025+
/// modified) and the hosted-only path.
1026+
#[test]
1027+
fn remove_hosted_revert_failure_prints_the_error_once() {
1028+
let needle = "annot restore pkg:npm/left-pad@1.3.0 to its upstream registry entry";
1029+
for manifest_backed in [true, false] {
1030+
let tmp = tempfile::tempdir().expect("tempdir");
1031+
let lock_path = tmp.path().join("package-lock.json");
1032+
std::fs::write(&lock_path, redirected_lock_text()).unwrap();
1033+
if manifest_backed {
1034+
write_manifest_files_empty(tmp.path(), NPM_PURL, NPM_UUID);
1035+
}
1036+
let (code, stdout, stderr) = run_remove(tmp.path(), &[NPM_PURL, "--yes", "--offline"], &[]);
1037+
assert_eq!(
1038+
code, 1,
1039+
"manifest_backed={manifest_backed}: stdout=\n{stdout}\nstderr=\n{stderr}"
1040+
);
1041+
assert_eq!(
1042+
stderr.matches(needle).count(),
1043+
1,
1044+
"manifest_backed={manifest_backed}: the refusal must print exactly once; \
1045+
stderr=\n{stderr}"
1046+
);
1047+
assert_eq!(
1048+
stderr.contains("The manifest was not modified."),
1049+
manifest_backed,
1050+
"manifest_backed={manifest_backed}: stderr=\n{stderr}"
1051+
);
1052+
}
1053+
}
1054+
10211055
// ---------------------------------------------------------------------------
10221056
// 10. Restored-lockfile write failure (main flow + hosted-only). Unix-only;
10231057
// skipped where directory modes are not enforced (root).

0 commit comments

Comments
 (0)