From 004042ab820536d1faf826ab6f5b27de22930d76 Mon Sep 17 00:00:00 2001 From: Jasper Date: Wed, 15 Jul 2026 10:42:23 +0200 Subject: [PATCH] fix: reject duplicate recipe parameter keys (#10448) --- crates/goose/src/recipe/validate_recipe.rs | 66 ++++++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/crates/goose/src/recipe/validate_recipe.rs b/crates/goose/src/recipe/validate_recipe.rs index 69af990db..b669ac059 100644 --- a/crates/goose/src/recipe/validate_recipe.rs +++ b/crates/goose/src/recipe/validate_recipe.rs @@ -94,12 +94,15 @@ fn validate_parameters_in_template( let mut template_variables = template_variables.clone(); template_variables.remove(BUILT_IN_RECIPE_DIR_PARAM); - let param_keys: HashSet = recipe_parameters - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|p| p.key.clone()) - .collect(); + let mut param_keys = HashSet::new(); + for parameter in recipe_parameters.as_deref().unwrap_or_default() { + if !param_keys.insert(parameter.key.clone()) { + return Err(anyhow::anyhow!( + "Duplicate parameter definition: {}.", + parameter.key + )); + } + } let missing_keys = template_variables .difference(¶m_keys) @@ -172,6 +175,57 @@ fn validate_optional_parameters(parameters: &Option>) -> Re mod tests { use super::*; + fn recipe_with_duplicate_parameter_keys(parameters: &str) -> String { + format!( + r#" +version: 1.0.0 +title: Duplicate parameters +description: Duplicate parameter validation +instructions: Test {{{{ value }}}} +parameters: +{parameters} +"# + ) + } + + #[test] + fn test_rejects_string_then_file_parameter_with_same_key() { + let recipe_content = recipe_with_duplicate_parameter_keys( + r#" - key: value + input_type: string + requirement: optional + default: file.txt + description: A string parameter + - key: value + input_type: file + requirement: required + description: A file parameter"#, + ); + + let error = validate_recipe_template_from_content(&recipe_content, None).unwrap_err(); + + assert_eq!(error.to_string(), "Duplicate parameter definition: value."); + } + + #[test] + fn test_rejects_file_then_string_parameter_with_same_key() { + let recipe_content = recipe_with_duplicate_parameter_keys( + r#" - key: value + input_type: file + requirement: required + description: A file parameter + - key: value + input_type: string + requirement: optional + default: file.txt + description: A string parameter"#, + ); + + let error = validate_recipe_template_from_content(&recipe_content, None).unwrap_err(); + + assert_eq!(error.to_string(), "Duplicate parameter definition: value."); + } + #[test] fn test_validate_recipe_template_from_content_success() { let recipe_content = r#"