fix(cli): reject unknown recommend filter values (#1114)
Some checks are pending
CI / Detect changes (push) Waiting to run
CI / Test Suite (push) Blocked by required conditions
CI / Test Suite-1 (push) Blocked by required conditions
CI / Test Suite-2 (push) Blocked by required conditions
CI / Rustfmt (push) Blocked by required conditions
CI / Clippy (push) Blocked by required conditions
CI / Cargo Check (push) Blocked by required conditions
CI / Python Checks (push) Blocked by required conditions
Release Please / release-please (push) Waiting to run

`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.
This commit is contained in:
Jay Hemnani 2026-10-07 20:46:39 +05:30 • committed by GitHub
parent 86083b0ca7
commit 35fdd4f941
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 12 deletions

View file

@ -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

View file

@ -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(&[