From 4b2575ae19e56a22b1fc8d68de197dfa69e7cf10 Mon Sep 17 00:00:00 2001 From: Scott McMaster Date: Tue, 12 May 2026 14:22:30 +0800 Subject: [PATCH 1/2] feat: Add channel registration check before searching to prevent errors in search_packages --- .../src-tauri/src/evolve/search_packages.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/native/src-tauri/src/evolve/search_packages.rs b/apps/native/src-tauri/src/evolve/search_packages.rs index dce77905d..077d4b6ea 100644 --- a/apps/native/src-tauri/src/evolve/search_packages.rs +++ b/apps/native/src-tauri/src/evolve/search_packages.rs @@ -32,6 +32,31 @@ pub struct SearchPackageResult { pub install_via: SearchResultInstallTarget, } +/// Determine prior to searching whether a channel is registered and can be searched, +/// to avoid unnecessary command execution and errors. +/// We can do this with the following command: +/// ```bash +/// nix registry list' +/// ``` +/// and check if the output contains the channel in a "flake:channel" format. +fn channel_is_registered(channel: &str) -> bool { + let mut cmd = Command::new("nix"); + cmd.args(["registry", "list"]); + + // Pipe this to grep to check if the channel is registered + let output = match cmd.output() { + Ok(output) => output, + Err(e) => { + log::error!("Failed to execute nix registry list: {}", e); + return false; + } + }; + + let stdout = String::from_utf8_lossy(&output.stdout); + // Important to have a space before and after the channel name to avoid partial matches (e.g. "nixpkgs-unstable" matching "nixpkgs") + stdout.lines().any(|line| line.contains(&format!(" flake:{} ", channel))) +} + /// Search a single channel and return a list of SearchPackageResult /// results. fn search_single_channel( @@ -159,6 +184,12 @@ fn collect_from_channels( return Ok(true); } + if !channel_is_registered(channel) { + // CONSIDER: Whether we need to surface this to the agent somehow. + log::warn!("Channel '{}' is not registered, skipping search for this channel", channel); + continue; + } + let channel_results = search_single_channel(config_dir, query_term, use_regex, channel)?; let complete = process_channel_results(structured, channel_results, limit)?; if complete { From db3009e02893409031271175379adb41cec33df6 Mon Sep 17 00:00:00 2001 From: Scott McMaster Date: Wed, 13 May 2026 09:44:14 +0800 Subject: [PATCH 2/2] fix: AI code review comments --- .../src-tauri/src/evolve/search_packages.rs | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/apps/native/src-tauri/src/evolve/search_packages.rs b/apps/native/src-tauri/src/evolve/search_packages.rs index 077d4b6ea..1e4c0b89a 100644 --- a/apps/native/src-tauri/src/evolve/search_packages.rs +++ b/apps/native/src-tauri/src/evolve/search_packages.rs @@ -32,29 +32,40 @@ pub struct SearchPackageResult { pub install_via: SearchResultInstallTarget, } -/// Determine prior to searching whether a channel is registered and can be searched, -/// to avoid unnecessary command execution and errors. -/// We can do this with the following command: -/// ```bash -/// nix registry list' -/// ``` -/// and check if the output contains the channel in a "flake:channel" format. -fn channel_is_registered(channel: &str) -> bool { +/// Wrapper for `nix registry list` that returns the raw output as a string, or an error if the command fails. +/// Clients can parse it themselves. +fn nix_registry_list(config_dir: &str) -> Result { let mut cmd = Command::new("nix"); - cmd.args(["registry", "list"]); + cmd.args(["registry", "list"]) + .current_dir(config_dir) + .env("PATH", crate::system::nix::get_nix_path()) + .env("NIX_CONFIG", "experimental-features = nix-command flakes"); - // Pipe this to grep to check if the channel is registered - let output = match cmd.output() { - Ok(output) => output, - Err(e) => { - log::error!("Failed to execute nix registry list: {}", e); - return false; - } - }; + let output = cmd.output()?; - let stdout = String::from_utf8_lossy(&output.stdout); - // Important to have a space before and after the channel name to avoid partial matches (e.g. "nixpkgs-unstable" matching "nixpkgs") - stdout.lines().any(|line| line.contains(&format!(" flake:{} ", channel))) + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let truncated_stderr = truncate_error(&stderr, 8000); + return Err(anyhow::anyhow!( + "nix registry list failed with status {:?}: {}", + output.status.code(), + truncated_stderr + )); + } + + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} + +/// Determine prior to searching whether a channel is registered and can be searched, +/// to avoid unnecessary command execution and errors. +/// Check if the output contains the channel in a "flake:channel" format. +fn channel_is_registered(registry_list: &str, channel: &str) -> bool { + // Split each line by whitespace and check if any token equals "flake:". + // Avoids partial matches. + let channel_pattern = format!("flake:{}", channel); + registry_list + .lines() + .any(|line| line.split_whitespace().any(|token| token == channel_pattern)) } /// Search a single channel and return a list of SearchPackageResult @@ -179,12 +190,14 @@ fn collect_from_channels( limit: u64, structured: &mut Vec, ) -> Result { + let registry_list = nix_registry_list(config_dir)?; + for channel in channels { if structured.len() >= limit as usize { return Ok(true); } - if !channel_is_registered(channel) { + if !channel_is_registered(®istry_list, channel) { // CONSIDER: Whether we need to surface this to the agent somehow. log::warn!("Channel '{}' is not registered, skipping search for this channel", channel); continue; @@ -429,4 +442,12 @@ mod tests { assert_eq!(structured.len(), 2, "expected deduplication by attr_path"); } + + #[test] + fn channel_registration_check() { + let registry_list = "global flake:agda github:agda/agd\nglobal flake:nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable\nglobal flake:nix github:NixOS/nix\n"; + assert!(channel_is_registered(registry_list, "nixpkgs")); + assert!(channel_is_registered(registry_list, "nix")); + assert!(!channel_is_registered(registry_list, "unregistered-channel")); + } }