From 1687a4b464bad1b9e633c65edfff5c90508b1341 Mon Sep 17 00:00:00 2001 From: sandikodev Date: Sun, 5 Apr 2026 00:20:07 +0700 Subject: [PATCH] fix(fs_write): guide model to read-before-write and prevent sed fallback Two related issues caused cascading failures during AI-assisted editing: 1. The fs_write tool description did not explicitly require the model to read the file before calling str_replace. The model would guess or reconstruct old_str from memory, causing 'no occurrences found' errors. 2. When str_replace failed, the model would fall back to shell commands like 'sed -i' which have no diff preview, no validation, and can silently corrupt files or affect unintended lines. Fix both by: - Adding explicit read-before-write instruction to the tool description: 'ALWAYS use fs_read to read the file content BEFORE calling str_replace' - Adding explicit anti-fallback instruction: 'If str_replace fails, do NOT fall back to shell commands like sed. Instead, re-read the file with fs_read and retry.' - Improving the 'no occurrences found' error message to include the same guidance so the model receives it at the point of failure - Improving the 'multiple occurrences' error message to suggest adding more context to make old_str unique --- crates/chat-cli/src/cli/chat/tools/fs_write.rs | 8 ++++++-- crates/chat-cli/src/cli/chat/tools/tool_index.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/chat-cli/src/cli/chat/tools/fs_write.rs b/crates/chat-cli/src/cli/chat/tools/fs_write.rs index 09a64058a1..38f53db7eb 100644 --- a/crates/chat-cli/src/cli/chat/tools/fs_write.rs +++ b/crates/chat-cli/src/cli/chat/tools/fs_write.rs @@ -143,12 +143,16 @@ impl FsWrite { style::Print("\n"), )?; match matches.len() { - 0 => return Err(eyre!("no occurrences of \"{old_str}\" were found")), + 0 => return Err(eyre!( + "no occurrences of \"{old_str}\" were found — \ + use fs_read to read the current file content and retry str_replace with the exact text. \ + Do NOT fall back to shell commands like sed." + )), 1 => { let file = file.replacen(old_str, new_str, 1); os.fs.write(&path, file).await?; }, - x => return Err(eyre!("{x} occurrences of old_str were found when only 1 is expected")), + x => return Err(eyre!("{x} occurrences of old_str were found when only 1 is expected — add more surrounding context to old_str to make it unique")), } }, FsWrite::Insert { diff --git a/crates/chat-cli/src/cli/chat/tools/tool_index.json b/crates/chat-cli/src/cli/chat/tools/tool_index.json index 6ebeb50334..0dce2a5730 100644 --- a/crates/chat-cli/src/cli/chat/tools/tool_index.json +++ b/crates/chat-cli/src/cli/chat/tools/tool_index.json @@ -118,7 +118,7 @@ }, "fs_write": { "name": "fs_write", - "description": "A tool for creating and editing files\n * The `create` command will override the file at `path` if it already exists as a file, and otherwise create a new file\n * The `append` command will add content to the end of an existing file, automatically adding a newline if the file doesn't end with one. The file must exist.\n Notes for using the `str_replace` command:\n * The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n * If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n * The `new_str` parameter should contain the edited lines that should replace the `old_str`.", + "description": "A tool for creating and editing files\n * The `create` command will override the file at `path` if it already exists as a file, and otherwise create a new file\n * The `append` command will add content to the end of an existing file, automatically adding a newline if the file doesn't end with one. The file must exist.\n Notes for using the `str_replace` command:\n * ALWAYS use `fs_read` to read the file content BEFORE calling `str_replace`. Never guess or reconstruct `old_str` from memory.\n * The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n * If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n * The `new_str` parameter should contain the edited lines that should replace the `old_str`.\n * If `str_replace` fails because `old_str` was not found, do NOT fall back to shell commands like `sed`. Instead, re-read the file with `fs_read` and retry with the correct content.", "input_schema": { "type": "object", "properties": {