{"record":{"id":"cf4b48561095ff32","repo":"nikivdev/code","slug":"empty-recipe-selector","errorCode":null,"errorMessage":"empty recipe selector","messagePattern":"empty recipe selector","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/recipe.rs","lineNumber":620,"sourceCode":"            let mut hay = String::new();\n            hay.push_str(&r.id);\n            hay.push(' ');\n            hay.push_str(&r.name);\n            hay.push(' ');\n            hay.push_str(&r.description);\n            if !r.tags.is_empty() {\n                hay.push(' ');\n                hay.push_str(&r.tags.join(\" \"));\n            }\n            hay.to_ascii_lowercase().contains(&needle)\n        })\n        .collect()\n}\n\nfn select_recipe<'a>(recipes: &'a [Recipe], selector: &str) -> Result<&'a Recipe> {\n    let normalized = selector.trim();\n    if normalized.is_empty() {\n        bail!(\"empty recipe selector\")\n    }\n\n    if let Some(recipe) = recipes.iter().find(|r| r.id == normalized) {\n        return Ok(recipe);\n    }\n\n    let lowered = normalized.to_ascii_lowercase();\n    let exact_name: Vec<&Recipe> = recipes\n        .iter()\n        .filter(|r| r.name.to_ascii_lowercase() == lowered)\n        .collect();\n    if exact_name.len() == 1 {\n        return Ok(exact_name[0]);\n    }\n    if exact_name.len() > 1 {\n        ambiguous_selector_error(selector, &exact_name)?;\n        bail!(\"ambiguous recipe selector\")\n    }","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/recipe.rs#L602-L638","documentation":"`select_recipe` rejects an empty (or whitespace-only) selector before doing any matching. This is a fast-fail input validation so callers get a precise message instead of a confusing 'no recipe matched' result.","triggerScenarios":"Calling `run_recipe` with `RecipeRunOpts.selector` set to \"\" or only whitespace — typically an unset/empty shell variable or an argument accidentally dropped from the command line.","commonSituations":"Unset environment variable interpolated into the command (`f recipe run \"$SEL\"`); scripted invocation where an argument was conditional and omitted; piping a name with only spaces due to a parsing bug.","solutions":["Pass a non-empty recipe id on the command line","In scripts, guard the variable: error out early if it is empty before invoking the tool","List recipes first to pick a valid id","Trim/validate user input before forwarding it as the selector"],"exampleFix":"# before\nSEL=\"\"; f recipe run \"$SEL\"   # empty recipe selector\n# after\n[ -n \"$SEL\" ] || { echo \"usage: run.sh <recipe-id>\"; exit 2; }\nf recipe run \"$SEL\"","handlingStrategy":"validation","validationCode":"// preflight: reject empty selector before invoking the CLI\nfn require_selector(arg: Option<&str>) -> Result<String, String> {\n    arg.map(str::trim)\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n        .ok_or_else(|| \"recipe id required: pass a non-empty <recipe-id>\".to_string())\n}","typeGuard":"fn has_selector(args: &[String]) -> bool {\n    args.get(1).map_or(false, |s| !s.trim().is_empty())\n}","tryCatchPattern":"match run() {\n    Err(e) if e.to_string().contains(\"empty recipe selector\") => {\n        eprintln!(\"usage: f recipe run <recipe-id>\");\n        std::process::exit(2);\n    }\n    other => other,\n}","preventionTips":["Default-check shell variables before interpolation (`${SEL:?unset}` in bash)","Always pass the selector as a positional argument, not via optional flags","In wrappers, validate argv length before exec'ing the tool","Trim user-supplied input before forwarding"],"tags":["input-validation","cli","recipe-selector"],"backgroundTag":"empty-selector-argument","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}