Skip to content

Commit 6efe9b1

Browse files
mikolalysenkoclaude
andcommitted
fix(scan,crawler): surface the pre-failure vendor reconcile in JSON; treat empty CARGO_HOME as unset
Two more fixes the sweep's own RED regression tests were pinning: * scan --vendor --json: reconcile_dropped mutates the on-disk ledger BEFORE staging, but a staging failure returned Err without the envelope — the JSON consumer saw only the error object and never learned entries had been reverted on disk. The step error now carries the envelope built so far and the JSON fold attaches it as `vendor`. (Human mode prints no per-event lines even on success; unchanged.) * cargo crawler: CARGO_HOME="" hit PathBuf::from("") and resolved registry/src against the CWD, silently crawling nothing. Empty now means unset (env_non_empty convention), falling back to ~/.cargo. Pinned by scan_vendor_staging_error_still_reports_the_reconcile and empty_cargo_home_falls_back_to_home_dot_cargo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f41b2d6 commit 6efe9b1

2 files changed

Lines changed: 41 additions & 16 deletions

File tree

crates/socket-patch-cli/src/commands/scan/vendor_flow.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,23 @@ async fn preview_vendor_json(cwd: &Path, selected: &[PatchSearchResult]) -> serd
6161
/// [`download_patch_records`]; no manifest involvement at all).
6262
///
6363
/// `Ok((has_errors, envelope))` on a run that reached the engine;
64-
/// `Err((code, message))` for the lock/stage/manifest failures the
65-
/// caller folds into its own output shape (scan's ad-hoc JSON can't use
66-
/// `acquire_or_emit`, which prints an Envelope).
64+
/// `Err((code, message, envelope))` for the lock/stage/manifest failures
65+
/// the caller folds into its own output shape (scan's ad-hoc JSON can't
66+
/// use `acquire_or_emit`, which prints an Envelope). The error carries
67+
/// the envelope built so far when the failure happened AFTER
68+
/// `reconcile_dropped` ran — the reconcile mutates the on-disk ledger,
69+
/// and its events must survive the error fold or the JSON consumer
70+
/// never learns about the mutation.
6771
async fn run_scan_vendor_step(
6872
common: &GlobalArgs,
6973
manifest_path: &Path,
7074
socket_dir: &Path,
7175
detached_records: Option<&HashMap<String, PatchRecord>>,
72-
) -> Result<(bool, Envelope), (&'static str, String)> {
76+
) -> Result<(bool, Envelope), (&'static str, String, Option<Box<Envelope>>)> {
7377
// The download phase created `.socket/` already in every flow that
7478
// reaches here, but `acquire` deliberately refuses to mkdir.
7579
if let Err(e) = tokio::fs::create_dir_all(socket_dir).await {
76-
return Err(("socket_dir_unwritable", e.to_string()));
80+
return Err(("socket_dir_unwritable", e.to_string(), None));
7781
}
7882
let guard = apply_lock::acquire(
7983
socket_dir,
@@ -83,8 +87,9 @@ async fn run_scan_vendor_step(
8387
apply_lock::LockError::Held => (
8488
"lock_held",
8589
"another socket-patch process is operating in this directory".to_string(),
90+
None,
8691
),
87-
apply_lock::LockError::Io { .. } => ("lock_io", e.to_string()),
92+
apply_lock::LockError::Io { .. } => ("lock_io", e.to_string(), None),
8893
})?;
8994

9095
let mut env = Envelope::new(EnvelopeCommand::Vendor);
@@ -111,7 +116,7 @@ async fn run_scan_vendor_step(
111116
drop(guard);
112117
return Ok((false, env));
113118
}
114-
Err(e) => return Err(("invalid_manifest", e.to_string())),
119+
Err(e) => return Err(("invalid_manifest", e.to_string(), None)),
115120
};
116121
// Same placement as the `vendor` command: dropped entries
117122
// are reverted even when zero in-scope patches remain.
@@ -123,12 +128,15 @@ async fn run_scan_vendor_step(
123128
match stage_vendor_sources_in_memory(common, &manifest, socket_dir, &common.cwd).await {
124129
Ok(MemStageOutcome::Ready(s)) => s,
125130
Ok(MemStageOutcome::Unavailable) => {
131+
// The reconcile above may have already reverted dropped
132+
// entries on disk — hand its envelope to the error fold.
126133
return Err((
127134
"no_local_source",
128135
"patch artifacts unavailable (offline or download failure)".to_string(),
129-
))
136+
Some(Box::new(env)),
137+
));
130138
}
131-
Err(e) => return Err(("stage_failed", e)),
139+
Err(e) => return Err(("stage_failed", e, Some(Box::new(env)))),
132140
};
133141
let sources = staged.as_patch_sources();
134142
has_errors |=
@@ -280,14 +288,21 @@ async fn run_vendor_json_path(
280288
serde_json::to_value(&venv).unwrap_or_else(|_| serde_json::json!({}));
281289
i32::from(has_errors)
282290
}
283-
Err((code, message)) => {
291+
Err((code, message, venv)) => {
284292
track_patch_vendor_failed(
285293
&message,
286294
args.common.dry_run,
287295
telemetry_token,
288296
telemetry_org,
289297
)
290298
.await;
299+
// A pre-failure reconcile already mutated the ledger on disk;
300+
// its envelope (events included) must reach the JSON consumer
301+
// even though the run aborts here.
302+
if let Some(venv) = venv {
303+
result["vendor"] =
304+
serde_json::to_value(&*venv).unwrap_or_else(|_| serde_json::json!({}));
305+
}
291306
result["status"] = serde_json::json!("error");
292307
result["error"] = serde_json::json!({
293308
"code": code,
@@ -378,7 +393,10 @@ async fn run_vendor_interactive_path(
378393
.await;
379394
i32::from(has_errors)
380395
}
381-
Err((code, message)) => {
396+
// Human mode prints no per-event lines even on success, so the
397+
// carried envelope has no human rendering to feed — JSON mode is
398+
// where the reconcile events must survive (see the JSON fold above).
399+
Err((code, message, _venv)) => {
382400
track_patch_vendor_failed(
383401
&message,
384402
args.common.dry_run,
@@ -534,7 +552,11 @@ fn boxed_scan_vendor_step<'a>(
534552
socket_dir: &'a Path,
535553
detached_records: Option<&'a HashMap<String, PatchRecord>>,
536554
) -> std::pin::Pin<
537-
Box<dyn std::future::Future<Output = Result<(bool, Envelope), (&'static str, String)>> + 'a>,
555+
Box<
556+
dyn std::future::Future<
557+
Output = Result<(bool, Envelope), (&'static str, String, Option<Box<Envelope>>)>,
558+
> + 'a,
559+
>,
538560
> {
539561
Box::pin(run_scan_vendor_step(
540562
common,

crates/socket-patch-core/src/crawlers/cargo_crawler.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,15 @@ impl CargoCrawler {
395395
Some((name.to_string(), version.to_string()))
396396
}
397397

398-
/// Get `CARGO_HOME`, defaulting to `$HOME/.cargo`.
398+
/// Get `CARGO_HOME`, defaulting to `$HOME/.cargo`. An empty value means
399+
/// unset (the env_non_empty convention) — `PathBuf::from("")` would
400+
/// otherwise resolve `registry/src` against the CWD and silently crawl
401+
/// nothing.
399402
fn cargo_home() -> PathBuf {
400-
if let Ok(cargo_home) = std::env::var("CARGO_HOME") {
401-
return PathBuf::from(cargo_home);
403+
match std::env::var("CARGO_HOME") {
404+
Ok(v) if !v.trim().is_empty() => PathBuf::from(v),
405+
_ => crate::utils::fs::home_dir().join(".cargo"),
402406
}
403-
crate::utils::fs::home_dir().join(".cargo")
404407
}
405408
}
406409

0 commit comments

Comments
 (0)