GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||
GeezerVison is showing github.com — Fix Hatch environments being invisible to stale-install checks and VEX (#335) by mikolalysenko · Pull Request #700 · SocketDev/socket-patch Conversation
|
|
[agent] CI note on Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
On macOS /var is a symlink to /private/var, so an activated env and the discovered one can name the same directory differently, and the stale-install check then missed it. Compare resolved paths as a fallback, and leave dirs.env.virtual unresolved as Hatch does (only an env's explicit path is resolved). Refs #335 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
An activated venv that is not one of Hatch's own is never used by hatch run, yet it returned from discovery before the Hatch envs were added, so a developer shell with any venv active hid the stale Hatch env again. Add Hatch's envs in that case too, and have the vendored probe judge Hatch's env prefixes directly. Refs #335 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Bugbot Autofix prepared a fix for the issue found in the latest run.
Or push these changes by commenting: Preview (d5f09ab674)diff --git a/crates/socket-patch-cli/src/commands/apply.rs b/crates/socket-patch-cli/src/commands/apply.rs --- a/crates/socket-patch-cli/src/commands/apply.rs +++ b/crates/socket-patch-cli/src/commands/apply.rs @@ -2,9 +2,7 @@ use socket_patch_core::api::blob_fetcher::get_missing_blobs; use socket_patch_core::api::client::{get_api_client_with_overrides, ApiClient}; use socket_patch_core::crawlers::ruby_crawler::config_path_ignored_warning; -use socket_patch_core::crawlers::{ - detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler, -}; +use socket_patch_core::crawlers::{detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler}; use socket_patch_core::manifest::operations::read_manifest; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::{ diff --git a/crates/socket-patch-cli/src/commands/list.rs b/crates/socket-patch-cli/src/commands/list.rs --- a/crates/socket-patch-cli/src/commands/list.rs +++ b/crates/socket-patch-cli/src/commands/list.rs @@ -431,7 +431,10 @@ detail: detail.clone(), }); } else if !args.common.silent { - eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail)); + eprintln!( + "Warning: {}", + crate::commands::rollback::capitalize_first(detail) + ); } } let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent); @@ -773,12 +776,18 @@ let listings = HostedListing::from_pins( &[ pin("pkg:npm/[email protected]", &record.uuid), - pin("pkg:npm/[email protected]", "33333333-3333-4333-8333-333333333333"), + pin( + "pkg:npm/[email protected]", + "33333333-3333-4333-8333-333333333333", + ), ], Some(&legacy), ); assert_eq!(listings[0].record, record); - assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333"); + assert_eq!( + listings[1].record.uuid, + "33333333-3333-4333-8333-333333333333" + ); assert!(listings[1].record.vulnerabilities.is_empty()); assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]); } diff --git a/crates/socket-patch-cli/src/commands/mod.rs b/crates/socket-patch-cli/src/commands/mod.rs --- a/crates/socket-patch-cli/src/commands/mod.rs +++ b/crates/socket-patch-cli/src/commands/mod.rs @@ -1,7 +1,7 @@ pub mod apply; pub(crate) mod bun_preflight; +pub(crate) mod composer_hints; pub(crate) mod context; -pub(crate) mod composer_hints; pub(crate) mod fetch_stage; pub mod get; pub mod hosted_bundle; @@ -9,11 +9,11 @@ pub(crate) mod lock_cli; pub mod remove; pub mod repair; -pub(crate) mod vendored_backend; pub mod rollback; pub mod scan; pub mod update; pub mod vendor; +pub(crate) mod vendored_backend; pub mod vex; pub(crate) mod vex_consumed; pub(crate) mod vex_sources; @@ -141,9 +141,11 @@ common: &crate::args::GlobalArgs, root: &Path, ) -> socket_patch_core::patch::redirect::RedirectState { - hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all( - &discover_wiring(common, root).await, - )) + hosted_state_from_pins( + &socket_patch_core::patch::redirect::upstream::HostedPin::all( + &discover_wiring(common, root).await, + ), + ) } /// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl @@ -153,10 +155,8 @@ ) -> socket_patch_core::patch::redirect::RedirectState { let mut state = socket_patch_core::patch::redirect::RedirectState::new(); for pin in pins { - state - .records - .entry(pin.purl.clone()) - .or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord { + state.records.entry(pin.purl.clone()).or_insert_with(|| { + socket_patch_core::manifest::schema::PatchRecord { uuid: pin.uuid.clone(), exported_at: String::new(), files: Default::default(), @@ -164,7 +164,8 @@ description: String::new(), license: String::new(), tier: String::new(), - }); + } + }); } state } @@ -191,4 +192,3 @@ } } } - diff --git a/crates/socket-patch-cli/src/commands/remove.rs b/crates/socket-patch-cli/src/commands/remove.rs --- a/crates/socket-patch-cli/src/commands/remove.rs +++ b/crates/socket-patch-cli/src/commands/remove.rs @@ -17,9 +17,9 @@ pin_before_hash_blobs, rollback_patches_inner, run_hosted_leg, sweep_failure, sweep_unused_artifacts, HostedLegOutcome, InnerSelection, }; -use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::args::{apply_env_toggles, GlobalArgs}; use crate::commands::lock_cli::acquire_or_emit; +use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::json_envelope::{Command, Envelope, EnvelopeError, PatchAction, PatchEvent, Status}; use crate::ui::plural; diff --git a/crates/socket-patch-cli/src/commands/rollback.rs b/crates/socket-patch-cli/src/commands/rollback.rs --- a/crates/socket-patch-cli/src/commands/rollback.rs +++ b/crates/socket-patch-cli/src/commands/rollback.rs @@ -10,13 +10,13 @@ }; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::select_installed_variants; +use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::patch::rollback::{ cannot_rollback_error, rollback_package_patch, verify_file_rollback, RollbackResult, VerifyRollbackResult, VerifyRollbackStatus, }; use socket_patch_core::telemetry::{track_patch_rollback_failed, track_patch_rolled_back}; use socket_patch_core::utils::purl::{patch_matches, strip_purl_qualifiers}; -use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::vendor::{purl_keys_cover, RevertOpts, VendorState}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -1026,7 +1026,8 @@ .iter() .map(|(code, detail)| (code.to_string(), detail.clone())), ); - out.edited_files.extend(outcome.reverted_files.iter().cloned()); + out.edited_files + .extend(outcome.reverted_files.iter().cloned()); let unwound: Vec<_> = vlt_targets .into_iter() .filter(|t| out.reverted.iter().any(|p| p == &t.purl)) @@ -1170,7 +1171,11 @@ } else if !args.common.silent { println!( "{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.", - if args.common.dry_run { "Would remove" } else { "Removed" }, + if args.common.dry_run { + "Would remove" + } else { + "Removed" + }, socket_patch_core::patch::redirect::REDIRECT_STATE_REL ); } diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs --- a/crates/socket-patch-cli/src/commands/scan/discovery.rs +++ b/crates/socket-patch-cli/src/commands/scan/discovery.rs @@ -168,29 +168,32 @@ } // `(ledger key, base purl, entry)`; the artifact fallback has no // entries to probe, so it never reports unwired keys. - let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> = - match state { - Ok(state) => state - .entries - .iter() - .map(|(key, entry)| { - ( - key.clone(), - strip_purl_qualifiers(&entry.base_purl).to_string(), - Some(entry), - ) - }) - .collect(), - // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): - // recover the vendored set from the committed artifacts, or - // `scan --prune` (whose ledger exemption also degrades to empty) - // would delete still-vendored packages' manifest entries and blobs. - Err(_) => vendored_purls_from_artifacts(common) - .await - .into_iter() - .map(|base| (base.clone(), base, None)) - .collect(), - }; + let candidates: Vec<( + String, + String, + Option<&socket_patch_core::vendor::VendorEntry>, + )> = match state { + Ok(state) => state + .entries + .iter() + .map(|(key, entry)| { + ( + key.clone(), + strip_purl_qualifiers(&entry.base_purl).to_string(), + Some(entry), + ) + }) + .collect(), + // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): + // recover the vendored set from the committed artifacts, or + // `scan --prune` (whose ledger exemption also degrades to empty) + // would delete still-vendored packages' manifest entries and blobs. + Err(_) => vendored_purls_from_artifacts(common) + .await + .into_iter() + .map(|base| (base.clone(), base, None)) + .collect(), + }; // Composer by release identity: a ledger `@3.0.2.0` is the crawled // `@3.0.2`, not a second package to supplement. let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned()); @@ -1038,7 +1041,9 @@ ..GlobalArgs::default() }; let state = socket_patch_core::vendor::load_state(root).await; - vendored_ledger_supplement(&args, crawled, &state).await.packages + vendored_ledger_supplement(&args, crawled, &state) + .await + .packages } /// A ledger entry vendored as `@3.0.2.0` is the crawled composer @@ -1073,7 +1078,9 @@ out.iter().map(|p| &p.purl).collect::<Vec<_>>() ); - let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages; + let out = vendored_ledger_supplement(&args, &[], &Ok(state)) + .await + .packages; assert_eq!( out.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(), vec!["pkg:composer/psr/[email protected]"] @@ -1176,7 +1183,10 @@ let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await; let out = vendored_ledger_supplement(&args, &[], &state).await; assert_eq!( - out.packages.iter().map(|p| p.purl.as_str()).collect::<Vec<_>>(), + out.packages + .iter() + .map(|p| p.purl.as_str()) + .collect::<Vec<_>>(), vec!["pkg:npm/[email protected]"], "lock={lock:?}" ); 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 @@ -932,7 +932,8 @@ socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok() }) }; - let rewrite_options = || RewriteOptions { + let rewrite_options = || { + RewriteOptions { dry_run: common.dry_run, targets_pipenv_lock, pipenv_major, @@ -944,6 +945,7 @@ npm_allow_remote_config: !common.no_npm_allow_remote_config, npm_outer: &npm_outer, blocking: true, + } }; // The rollout gate plans again without its deferred rows: keep what // the second pass needs. @@ -2304,13 +2306,19 @@ /// artifacts, then verify with `vex`. After a vendored→hosted takeover /// (`vendored_removed`) the commit also has to carry the deleted vendored /// ledger entries and artifacts. -fn format_next_steps(files: &[String], edits: &[socket_patch_core::patch::redirect::FileEdit], vendored_removed: bool) -> Vec<String> { +fn format_next_steps( + files: &[String], + edits: &[socket_patch_core::patch::redirect::FileEdit], + vendored_removed: bool, +) -> Vec<String> { if files.is_empty() && !vendored_removed { return Vec::new(); } let mut commit: Vec<String> = Vec::new(); if vendored_removed { - commit.push(".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string()); + commit.push( + ".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string(), + ); } commit.extend(files.iter().cloned()); let npm = files @@ -4391,19 +4399,43 @@ use super::npm_allow_remote_one_line; let hosts = ["patch.socket.dev"]; let cases = [ - (npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"), - (npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"), - (npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"), - (npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"), - (npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"), - (npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"), + ( + npm_allow_remote_configured_detail(&hosts, true, false), + "Note: set", + ), + ( + npm_allow_remote_configured_detail(&hosts, false, false), + "Note: set", + ), + ( + npm_allow_remote_configured_detail(&hosts, true, true), + "Note: would set", + ), + ( + npm_allow_remote_already_detail(&hosts), + "Note: .npmrc already", + ), + ( + npm_allow_remote_user_set_detail(&hosts, "none"), + "Warning: npm >=12", + ), + ( + npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), + "Warning: npm >=12", + ), (npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"), - (npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"), + ( + npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), + "Warning: npm >=12", + ), ]; for (detail, start) in cases { let line = npm_allow_remote_one_line(&detail); assert!(line.starts_with(start), "{line}"); - assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}"); + assert!( + !line.contains('\n') && line.ends_with("(details: --verbose)."), + "{line}" + ); } } } diff --git a/crates/socket-patch-cli/src/commands/scan/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -35,17 +35,17 @@ use super::get::{download_and_apply_patches_with, DownloadParams, DownloadRun}; +use self::policy::{load_invocation_policy, InvocationPolicy, PolicyLoadError, ScanPolicy}; pub use self::socket_yml_args::{SocketYmlArgs, MIN_SEVERITY_ENV}; -use self::policy::{load_invocation_policy, InvocationPolicy, PolicyLoadError, ScanPolicy}; mod discovery; mod gc; pub(crate) mod hosted; pub(crate) mod policy; -mod socket_yml_args; pub(crate) mod render; pub(crate) mod rollout; pub mod rollout_args; +mod socket_yml_args; pub(crate) mod vendor_flow; use self::discovery::{ @@ -65,13 +65,13 @@ pub(crate) use self::hosted::boxed_run_redirect_selected; use self::hosted::run_redirect; pub(crate) use self::hosted::{vlt_rollback_heal, vlt_takeover_heal}; -pub(crate) use self::vendor_flow::{ - boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, -}; use self::vendor_flow::{ boxed_vendor_interactive_path, boxed_vendor_json_path, fold_vendored_skips_into_apply, partition_skipped_selected, }; +pub(crate) use self::vendor_flow::{ + boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, +}; /// Packages per batch request on the authenticated API when `--batch-size` /// is not given: the server's own per-request maximum @@ -318,11 +318,7 @@ /// `requests`), or a purl with or without its version /// (`pkg:npm/lodash`, `pkg:pypi/[email protected]`). Repeat the flag or /// separate with commas - #[arg( - long = "package", - env = "SOCKET_SCAN_PACKAGES", - value_delimiter = ',' - )] + #[arg(long = "package", env = "SOCKET_SCAN_PACKAGES", value_delimiter = ',')] pub packages: Vec<String>, /// On a successful scan, also generate an OpenVEX 0.2.0 document. @@ -500,9 +496,10 @@ telemetry.flush().await; let error_count = failures.len(); if error_count > 0 && error_count == packages.len() { - let err = failures - .last() - .map_or_else(|| "all patch-detail queries failed".to_string(), |(_, e)| e.clone()); + let err = failures.last().map_or_else( + || "all patch-detail queries failed".to_string(), + |(_, e)| e.clone(), + ); let message = format!("all {error_count} patch-detail queries failed: {err}"); if detail_error_line { eprintln!("{}", render::fetch_details_failed(&failures)); @@ -568,7 +565,11 @@ packages: &[BatchPackagePatches], result: Option<&mut serde_json::Value>, ) -> Vec<rollout::Row> { - let failed: Vec<String> = discovered.failed.iter().map(|(purl, _)| purl.clone()).collect(); + let failed: Vec<String> = discovered + .failed + .iter() + .map(|(purl, _)| purl.clone()) + .collect(); stage.incomplete = rollout::lookup_incomplete(&recorded.index, &failed, batch_failed); let rows = rollout::classify(&discovered.offers, &recorded.index, &stage.project); if let Some(result) = result { @@ -1317,7 +1318,8 @@ let joined = cwd.join(raw); if raw.contains(['*', '?', '[']) { let pattern = joined.to_string_lossy().into_owned(); - let matches = glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; + let matches = + glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; let before = dirs.len(); dirs.extend( matches @@ -1390,7 +1392,10 @@ } // One budget per invocation (§5.2): the directories spend it in sorted // order, and a package admitted in one is admitted free in the next. - let configured = match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { + let configured = match args + .rollout + .resolve_from_env(invocation.policy.max_new_patches()) + { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1491,7 +1496,10 @@ // error. let configured_cap = match args.rollout.carry.as_ref() { Some(carry) => carry.lock().configured, - None => match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { + None => match args + .rollout + .resolve_from_env(invocation.policy.max_new_patches()) + { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1499,11 +1507,8 @@ } }, }; - let mut stage = rollout::Stage::new( - configured_cap, - args.rollout.carry.clone(), - &args.common.cwd, - ); + let mut stage = + rollout::Stage::new(configured_cap, args.rollout.carry.clone(), &args.common.cwd); // Strict airgap (CLI_CONTRACT.md `--offline`): scan's patch discovery // is remote data, so refuse before the crawl and before the API client @@ -1704,8 +1709,11 @@ .filter(|pkg| args.common.purl_ecosystem_selected(&pkg.purl)) .collect(); - let package_specs: Vec<&String> = - args.packages.iter().filter(|s| !s.trim().is_empty()).collect(); + let package_specs: Vec<&String> = args + .packages + .iter() + .filter(|s| !s.trim().is_empty()) + .collect(); let filtered_crawled: Vec<_> = if package_specs.is_empty() { filtered_crawled } else { @@ -1860,13 +1868,12 @@ // `redirectState` rides the empty-discovery envelope too // (same rule as the ≥1-package path). `wiringLive` is empty // by construction: this run covered zero packages. - let redirect_state = (!args.common.is_global()).then_some( - crate::commands::hosted_state_from_pins( + let redirect_state = + (!args.common.is_global()).then_some(crate::commands::hosted_state_from_pins( &socket_patch_core::patch::redirect::upstream::HostedPin::all( ctx.discovery().await, ), - ), - ); + )); if let Some(state) = redirect_state_json(redirect_state.as_ref(), &[]) { result["redirectState"] = state; } @@ -2222,7 +2229,8 @@ // A report-only run selects nothing, but a severity floor or // `enabled: false` still hides candidates; report them like the // human arm does (the detail fetch runs only then). - if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() { + if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() + { if let Err((code, message)) = discover_selected( &api_client, &all_packages_with_patches, @@ -2515,12 +2523,7 @@ &all_packages_with_patches, None, ); - updates = offer_updates( - &rows, - &discovered, - &recorded, - &all_packages_with_patches, - ); + updates = offer_updates(&rows, &discovered, &recorded, &all_packages_with_patches); rows } // `discover_selected` already printed the failure to stderr. @@ -2982,14 +2985,20 @@ dirs.iter() .map(|(d, explicit)| { ( - d.strip_prefix(tmp.path()).unwrap().to_string_lossy().replace('\\', "/"), + d.strip_prefix(tmp.path()) + .unwrap() + .to_string_lossy() + .replace('\\', "/"), *explicit, ) }) .collect() }; - let got = project_dirs(tmp.path(), &["apps/*".into(), "libs/core".into(), "apps/web".into()]) - .unwrap(); + let got = project_dirs( + tmp.path(), + &["apps/*".into(), "libs/core".into(), "apps/web".into()], + ) + .unwrap(); // Named literally = explicit (also when a glob matches it too). assert_eq!( rel(got), diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs --- a/crates/socket-patch-cli/src/commands/scan/policy.rs +++ b/crates/socket-patch-cli/src/commands/scan/policy.rs @@ -11,9 +11,9 @@ use socket_patch_core::api::types::PatchSearchResult; use socket_patch_core::manifest::schema::PatchManifest; use socket_patch_core::policy::{ - canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name, - DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy, - PATCHES_DISABLED, + canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked, + sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError, + PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED, }; use socket_patch_core::utils::purl::normalize_purl; @@ -42,12 +42,18 @@ /// Load the policy for `args` (4.5): `--global` scans have no repo and read /// no file; everything else reads the repo root's socket.yml. pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result<InvocationPolicy, PolicyLoadError> { - let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?; + let overrides = args + .socket_yml + .overrides() + .map_err(PolicyLoadError::Usage)?; let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone()); if args.common.is_global() { - let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides) - .map_err(PolicyLoadError::Policy)? - .0; + let policy = SelectionPolicy::load( + &socket_patch_core::policy::MemoryPolicyFs::default(), + &overrides, + ) + .map_err(PolicyLoadError::Policy)? + .0; return Ok(InvocationPolicy { policy, repo_root: cwd, @@ -56,8 +62,8 @@ }); } let (repo_root, mut warnings) = find_repo_root_with_warnings(&cwd); - let (policy, load_warnings) = - SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides).map_err(PolicyLoadError::Policy)?; + let (policy, load_warnings) = SelectionPolicy::load(&DiskPolicyFs::new(&repo_root), &overrides) + .map_err(PolicyLoadError::Policy)?; warnings.extend(load_warnings); Ok(InvocationPolicy { policy, @@ -138,7 +144,12 @@ impl ScanPolicy { /// The policy for the project rooted at `root_dir`. - pub(crate) fn for_root(invocation: &InvocationPolicy, root_dir: &Path, explicit: bool, global: bool) -> Self { + pub(crate) fn for_root( + invocation: &InvocationPolicy, + root_dir: &Path, + explicit: bool, + global: bool, + ) -> Self { let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf()); let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default(); let root_verdict = if global { @@ -171,7 +182,9 @@ severity: None, }); } - let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed); + let announce_warnings = !invocation + .warned + .swap(true, std::sync::atomic::Ordering::Relaxed); Self { policy: invocation.policy.clone(), warnings, @@ -224,7 +237,10 @@ /// exclude stays in the query (so `upgradeAvailable` can be reported) /// but joins the retained set, which never reaches a writer. pub(crate) fn admit_crawled(&self, purl: &str) -> bool { - let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl)); + let verdict = self + .root_verdict + .clone() + .and_then(|()| self.policy.admits_purl(purl)); let reason = match verdict { Ok(()) => return true, Err(reason) => reason, @@ -334,7 +350,8 @@ // (not when a lower-ranked admitted patch simply wins). let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0])); if let Err(reason) = top_withheld { - let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); + let upgrade_withheld = + chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); if chosen.is_none() || upgrade_withheld { report.filtered.push(FilteredEntry { purl: Some(canon(&purl)), @@ -522,17 +539,20 @@ let verdict = if !policy.enabled() { Err(FilterReason::Disabled) } else { - root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| { - // The floor only hides a package when none of its patches pass. - match group - .iter() - .map(|p| policy.admits_severity(patch_severity_order(p))) - .find(Result::is_ok) - { - Some(ok) => ok, - None => policy.admits_severity(patch_severity_order(group[0])), - } - }) + root_verdict + .clone() + .and_then(|()| policy.admits_purl(purl)) + .and_then(|()| { + // The floor only hides a package when none of its patches pass. + match group + .iter() + .map(|p| policy.admits_severity(patch_severity_order(p))) + .find(Result::is_ok) + { + Some(ok) => ok, + None => policy.admits_severity(patch_severity_order(group[0])), + } + }) }; if let Err(reason) = verdict { out.push(( diff --git a/crates/socket-patch-cli/src/commands/scan/render.rs b/crates/socket-patch-cli/src/commands/scan/render.rs --- a/crates/socket-patch-cli/src/commands/scan/render.rs +++ b/crates/socket-patch-cli/src/commands/scan/render.rs @@ -746,7 +746,10 @@ #[test] fn report_only_hint_names_agent_mode() { - assert_eq!(report_only_hint()[0], "To apply these patches in place, run:"); + assert_eq!( + report_only_hint()[0], + "To apply these patches in place, run:" + ); assert!(report_only_hint()[1].contains("--mode agent")); } diff --git a/crates/socket-patch-cli/src/commands/scan/rollout.rs b/crates/socket-patch-cli/src/commands/scan/rollout.rs --- a/crates/socket-patch-cli/src/commands/scan/rollout.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout.rs @@ -4,8 +4,10 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; -use socket_patch_core::rollout::{canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan}; pub(crate) use socket_patch_core::rollout::stage::*; +use socket_patch_core::rollout::{ + canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan, +}; use super::discovery::UpdateInfo; @@ -208,11 +210,11 @@ mod tests { use super::*; use socket_patch_core::api::types::PatchSearchResult; + use socket_patch_core::api::types::VulnerabilityResponse; use socket_patch_core::manifest::schema::PatchManifest; - use std::path::Path; - use socket_patch_core::api::types::VulnerabilityResponse; use socket_patch_core::manifest::schema::PatchRecord; use std::collections::HashMap; + use std::path::Path; fn offer(purl: &str, uuid: &str, published: &str, severities: &[&str]) -> PatchSearchResult { PatchSearchResult { @@ -357,13 +359,21 @@ let stored = manifest(&[("pkg:composer/psr/[email protected]", "old")]); let recorded = RecordedIndex::new(Some(&stored), &[]); let offers = offers_from_results( - &[offer("pkg:composer/psr/[email protected]", "new", "2026-02-01T00:00:00Z", &["high"])], + &[offer( + "pkg:composer/psr/[email protected]", + "new", + "2026-02-01T00:00:00Z", + &["high"], + )], false, ); let rows = classify(&offers, &recorded, ""); let plan = socket_patch_core::rollout::plan_rollout( rows.into_iter().map(|row| row.candidate).collect(), - &MaxNew { value: Some(0), source: MaxNewSource::Flag }, + &MaxNew { + value: Some(0), + source: MaxNewSource::Flag, + }, false, &BTreeSet::new(), ); diff --git a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs --- a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs @@ -1,7 +1,6 @@ //! `scan --max-new-patches` (see the rollout guide, //! `docs/configuration.md#gradual-rollout`). - use clap::Args; pub(crate) use socket_patch_core::rollout::stage::RolloutCarry; use socket_patch_core::rollout::{resolve_max_new, MaxNew}; @@ -77,7 +76,6 @@ } } - #[cfg(test)] mod tests { use super::*; diff --git a/crates/socket-patch-cli/src/commands/vendor.rs b/crates/socket-patch-cli/src/commands/vendor.rs --- a/crates/socket-patch-cli/src/commands/vendor.rs +++ b/crates/socket-patch-cli/src/commands/vendor.rs @@ -257,10 +257,7 @@ ... diff truncated: showing 800 of 6941 lines You can send follow-ups to the cloud agent here. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Ready for review (burn-down agent).
Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
main restructured the activated-venv step so Poetry projects go through poetry_project_site_packages; keep that and add Hatch's out-of-tree envs to the non-Poetry activated-venv result, which is what this branch did before the restructure (#335). In CLI_CONTRACT.md keep main's reworded pypi_pipenv_stale_install row and this branch's pypi_hatch_stale_install row. Co-Authored-By: Claude <[email protected]>
|
bugbot run Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
bugbot run Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
main added a pipenv_lock parameter to the hosted Python stale-install probe; the Hatch remedy test from this branch still called it with the old arity, so the CLI test build broke after the merge. Refs #335 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
A Hatch project's pyproject alone reads as a uv project, so a set UV_PROJECT_ENVIRONMENT returned from discovery before Hatch's envs were added, hiding the env hatch run uses again. Add them on that path too. Refs #335 Assisted-by: Claude Code:claude-opus-5-5
Instead of appending Hatch's envs at each early return, wrap the whole local discovery so a recorded PDM/uv env, an activated venv, Pipenv's or Poetry's resolution all keep the project's Hatch envs visible to stale-install checks, agent mode and VEX. Refs #335 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
A project's [tool.hatch.envs.hatch-test] table is layered over Hatch's built-in hatch-test config, so the default Python matrix still applies unless the project sets its own matrix. The ~/.virtualenvs lookup skipped hatch-test.* whenever the table existed at all, which hid those envs from the stale-install checks and VEX. It now skips them only when the project defines its own hatch-test matrix. Checked against Hatch 1.18.1's `hatch env show`. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
[agent] Every red check on 97ce9b5 is a job GitHub never started. The jobs are cancelled with "The job was not acquired by Runner of type hosted even after multiple attempts", across CI, Bun, Go, vlt, PDM, Pipenv, npm and pnpm, and the queue is backed up for other branches too. vlt I re-ran the failed jobs once in CI, Bun, Go, vlt, PDM and Pipenv. npm and pnpm already had their one re-run at 20:16 and were starved again, so they will re-run on the next push. Nothing in the diff needs to change for these. Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Brings in the three commits that landed on main since the last merge, so CI re-runs on this branch after the runner outage cancelled most of the previous run. Assisted-by: Claude Code:claude-opus-5-5
rustfmt the two files this branch touches so they match the repository's formatting; no behavior change. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem 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 2 potential issues.
Bugbot Autofix prepared a fix for the issue found in the latest run.
Or push these changes by commenting:
@cursor push e7c9282626
diff --git a/crates/socket-patch-core/src/crawlers/hatch_env.rs b/crates/socket-patch-core/src/crawlers/hatch_env.rs --- a/crates/socket-patch-core/src/crawlers/hatch_env.rs +++ b/crates/socket-patch-core/src/crawlers/hatch_env.rs @@ -119,8 +119,8 @@ let Some(env_dir) = virtual_env_dir(&root, config.as_ref(), var) else { return found; }; - let in_project = env_dir.starts_with(&root) - || std::fs::canonicalize(&env_dir).is_ok_and(|d| d.starts_with(&root)); + let env_dir = std::fs::canonicalize(&env_dir).unwrap_or(env_dir); + let in_project = env_dir.starts_with(&root); let shared_flat = home_dir(var).is_some_and(|h| same_path(&env_dir, &h.join(".virtualenvs"))); for id in project_ids(&root) {
You can send follow-ups to the cloud agent here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
A relative [dirs.env] virtual such as ../envs still started with the project path, so discovery treated it as an in-project flat directory. It then claimed every venv in the parent directory and missed the nested <name>/<id>/<env> envs Hatch actually uses there. Hatch tests `root in data_directory.resolve().parents`, so the directory is now resolved like Python's Path.resolve() first, and the project root itself no longer counts as inside the project. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
canonicalize returns verbatim \\?\ paths on Windows. In those, `/` is not a separator and the OS does not fold `..`, so a virtual = "../envs" joined onto the project root stayed one opaque component. It still counted as inside the project, and reading the directory found nothing. Config paths are now joined component by component, and resolve() folds `.` and `..` before it canonicalizes the longest existing prefix. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
[agent] Ready for review at head
Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Bring the Hatch environment discovery branch up to date with main so it can merge cleanly. The only conflict was the crawlers module list, where this branch adds `hatch_env` and main added `ivy_cache`; both modules are kept. Co-Authored-By: Claude <[email protected]>
|
bugbot run Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
bugbot run Generated by Claude Code |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem 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 is ON, but it could not run because the branch was deleted or merged before autofix could start.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 25c8fbd. Configure here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| .as_array()? | ||
| .iter() | ||
| .filter_map(|v| v.as_str()) | ||
| .collect(); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Medium Severity
matrix_names keeps only TOML strings, so integer or boolean matrix values become no variants. In the shared ~/.virtualenvs layout those names are the only probe keys, so a stale env Hatch actually created is never found and the #335 warning never fires.
Reviewed by Cursor Bugbot for commit 25c8fbd. Configure here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Hosted Hatch rewrite leaves an existing Hatch environment unpatched with no stale-install warning, and vex still attests not_affected
There was an error while loading. Please reload this page.