fix: reject no-op edit_file replacements

This commit is contained in:
xiedongsheng authored and Hunter Bown committed 2026-05-11 13:09:22 -05:00
1 parent 195388b62b
commit 1352a65ac2
1 file changed
+36
+36
View File
@@ -331,6 +331,12 @@ impl ToolSpec for EditFileTool {
let search = required_str(&input, "search")?;
let replace = required_str(&input, "replace")?;
if search == replace {
return Err(ToolError::invalid_input(
"search and replace are identical, no change intended",
));
}
let file_path = context.resolve_path(path_str)?;
let contents = fs::read_to_string(&file_path).map_err(|e| {
@@ -716,6 +722,36 @@ mod tests {
assert!(err.to_string().contains("not found"));
}
#[tokio::test]
async fn test_edit_file_rejects_identical_search_and_replace() {
let tmp = tempdir().expect("tempdir");
let ctx = ToolContext::new(tmp.path().to_path_buf());
let test_file = tmp.path().join("same.txt");
fs::write(&test_file, "a := \"foo\"").expect("write");
let tool = EditFileTool;
let result = tool
.execute(
json!({
"path": "same.txt",
"search": "a := \"foo\"",
"replace": "a := \"foo\""
}),
&ctx,
)
.await;
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("search and replace are identical"),
"error must explain the no-op input: {err}"
);
let unchanged = fs::read_to_string(&test_file).expect("read");
assert_eq!(unchanged, "a := \"foo\"");
}
/// #157 — When the model uses `replacement` instead of `replace`,
/// the error should name the provided fields so the model can
/// self-correct without a second round-trip.