diff --git a/nanobot/agent/tools/filesystem.py b/nanobot/agent/tools/filesystem.py index ac52ca868..ea84fedb1 100644 --- a/nanobot/agent/tools/filesystem.py +++ b/nanobot/agent/tools/filesystem.py @@ -827,7 +827,8 @@ class EditFileTool(_FsTool): def description(self) -> str: return ( "Perform a small, exact replacement in one file by replacing " - "old_text with new_text. Use this for narrow text substitutions " + "old_text with new_text. When replacing text in an existing file, " + "old_text and new_text must be different. Use this for narrow text substitutions " "with old_text copied from read_file. For multi-file, structural, " "or generated code edits, prefer apply_patch. If old_text matches " "multiple times, provide more context or set occurrence, line_hint, " @@ -862,9 +863,12 @@ class EditFileTool(_FsTool): return ToolResult.error("Error: expected_replacements must be >= 1.") fp = self._resolve_write(path) + file_exists = fp.exists() + if file_exists and old_text == new_text: + return ToolResult.error("Error: new_text must be different from old_text.") # Create-file semantics: old_text='' + file doesn't exist → create - if not fp.exists(): + if not file_exists: if old_text == "": fp.parent.mkdir(parents=True, exist_ok=True) fp.write_text(new_text, encoding="utf-8") diff --git a/tests/tools/test_filesystem_tools.py b/tests/tools/test_filesystem_tools.py index b221bfcc1..a1e152c59 100644 --- a/tests/tools/test_filesystem_tools.py +++ b/tests/tools/test_filesystem_tools.py @@ -133,6 +133,16 @@ class TestEditFileTool: assert "Successfully" in result assert f.read_text() == "hello earth" + @pytest.mark.asyncio + async def test_identical_replacement_returns_clear_error(self, tool, tmp_path): + f = tmp_path / "a.py" + f.write_text("hello world", encoding="utf-8") + + result = await tool.execute(path=str(f), old_text="world", new_text="world") + + assert result == "Error: new_text must be different from old_text." + assert f.read_text(encoding="utf-8") == "hello world" + @pytest.mark.asyncio async def test_crlf_normalisation(self, tool, tmp_path): f = tmp_path / "crlf.py"