From 35fdd4f941f75e991a6a53a02efaae73a6e04582 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:46:39 +0530 Subject: [PATCH] fix(cli): reject unknown recommend filter values (#1114) `recommend` silently ignored an unrecognized --use-case or --runtime and fell back to marginal for an unrecognized --min-fit, so a typo such as `--use-case embeddings` returned unfiltered results with exit 0. These now exit 1 with the valid options, like --force-runtime already does. --- llmfit-tui/src/main.rs | 41 +++++++++++++++++++++++++---------- llmfit-tui/tests/cli_smoke.rs | 33 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/llmfit-tui/src/main.rs b/llmfit-tui/src/main.rs index 97304030..202eaf64 100644 --- a/llmfit-tui/src/main.rs +++ b/llmfit-tui/src/main.rs @@ -2184,7 +2184,13 @@ fn run_recommend( "perfect" => llmfit_core::fit::FitLevel::Perfect, "good" => llmfit_core::fit::FitLevel::Good, "marginal" => llmfit_core::fit::FitLevel::Marginal, - _ => llmfit_core::fit::FitLevel::Marginal, + other => { + eprintln!( + "Unknown --min-fit '{}'. Valid options: perfect, good, marginal", + other + ); + std::process::exit(1); + } }; fits.retain(|f| match (min_level, f.fit_level) { (llmfit_core::fit::FitLevel::Marginal, llmfit_core::fit::FitLevel::TooTight) => false, @@ -2214,23 +2220,34 @@ fn run_recommend( "bitnetcpp" | "bitnet.cpp" | "bitnet" => { fits.retain(|f| f.runtime == llmfit_core::fit::InferenceRuntime::BitNet) } - _ => {} // "any" or unrecognized — keep all + "any" => {} + other => { + eprintln!( + "Unknown --runtime '{}'. Valid options: mlx, llamacpp, vllm, bitnetcpp, any", + other + ); + std::process::exit(1); + } } // Filter by use case if specified if let Some(ref uc) = use_case { let target = match uc.to_lowercase().as_str() { - "coding" | "code" => Some(llmfit_core::models::UseCase::Coding), - "reasoning" | "reason" => Some(llmfit_core::models::UseCase::Reasoning), - "chat" => Some(llmfit_core::models::UseCase::Chat), - "multimodal" | "vision" => Some(llmfit_core::models::UseCase::Multimodal), - "embedding" | "embed" => Some(llmfit_core::models::UseCase::Embedding), - "general" => Some(llmfit_core::models::UseCase::General), - _ => None, + "coding" | "code" => llmfit_core::models::UseCase::Coding, + "reasoning" | "reason" => llmfit_core::models::UseCase::Reasoning, + "chat" => llmfit_core::models::UseCase::Chat, + "multimodal" | "vision" => llmfit_core::models::UseCase::Multimodal, + "embedding" | "embed" => llmfit_core::models::UseCase::Embedding, + "general" => llmfit_core::models::UseCase::General, + other => { + eprintln!( + "Unknown --use-case '{}'. Valid options: general, coding, reasoning, chat, multimodal, embedding", + other + ); + std::process::exit(1); + } }; - if let Some(target_uc) = target { - fits.retain(|f| f.use_case == target_uc); - } + fits.retain(|f| f.use_case == target); } // Filter by capability if specified diff --git a/llmfit-tui/tests/cli_smoke.rs b/llmfit-tui/tests/cli_smoke.rs index 5e994071..8b7df12c 100644 --- a/llmfit-tui/tests/cli_smoke.rs +++ b/llmfit-tui/tests/cli_smoke.rs @@ -411,6 +411,39 @@ fn recommend_capability_filter_does_not_ignore_unknown_or_tts() { ); } +#[test] +fn recommend_rejects_unknown_filter_values() { + for (flag, value) in [ + ("--use-case", "not_a_use_case"), + ("--min-fit", "not_a_fit"), + ("--runtime", "not_a_runtime"), + ] { + let output = Command::cargo_bin("llmfit") + .expect("failed to locate llmfit test binary") + .args([ + "--no-dashboard", + "--memory", + "8G", + "--ram", + "16G", + "--cpu-cores", + "4", + "recommend", + flag, + value, + ]) + .output() + .expect("failed to run llmfit"); + + assert_eq!(output.status.code(), Some(1), "{flag} {value} should fail"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains(&format!("Unknown {flag} '{value}'")), + "unexpected stderr for {flag}: {stderr}" + ); + } +} + #[test] fn fit_json_returns_empty_models_when_no_perfect_matches() { let json = run_json_command(&[