File Editing Methods
The five fundamental approaches AI coding agents use to modify your files, from brute-force rewrites to surgical precision.
1. Whole File Replacement
High Token Cost 100% ReliableWhat It Does
Completely overwrites the target file with new content. The agent provides the file path and the entire new content. Simple, brute-force, and guaranteed to work.
How It Works
writeFile(path, entireNewContent)
Example (Cline's XML Format)
<write_to_file>
<path>src/utils/helper.js</path>
<content>
// Entire file content goes here
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
</content>
</write_to_file>
When to Use
- Creating new files — No existing content to match
- Scaffolding/Boilerplate — Generating entire templates
- Small files (< 100 lines) — Token cost is negligible
- Massive refactors (> 50% changed) — More efficient than many edits
- Last resort fallback — When surgical methods fail
The Danger of Full Rewrites
If the AI doesn't have the complete file in context (or forgets parts), it might accidentally delete existing code. This is why agents like Cline recommend reading the file first and only use this as a fallback.
Who Uses It
All agents support this as a creation tool and fallback. MCP-SuperAssistant uses it as the primary method (the "minimalist" approach).
2. Search & Replace Blocks
Low Token Cost Requires Exact MatchWhat It Does
Surgical edits using a "Find this exact text" and "Replace with this" pattern. The AI specifies a block of code to find and what to replace it with.
Format Variations
Different agents use different markers, but the concept is the same:
app.py
```python
<<<<<<< SEARCH
def hello():
print("world")
=======
def hello():
print("Hello, world!")
>>>>>>> REPLACE
```
Uses git-conflict-style markers. Requires 5-9 angle brackets.
<replace_in_file>
<path>src/app.py</path>
<diff>
------- SEARCH
def hello():
print("world")
=======
def hello():
print("Hello, world!")
+++++++ REPLACE
</diff>
</replace_in_file>
XML wrapper with dash/plus markers. Supports legacy <<<< format too.
EditTool({
filePath: "/abs/path/to/app.py",
oldString: `def hello():
print("world")`,
newString: `def hello():
print("Hello, world!")`,
replaceAll: false
})
JSON/TypeScript-style arguments. replaceAll flag for bulk replacements.
The Critical Rule
SEARCH Must Match Exactly
The search block must match the file content character-for-character, including whitespace, indentation, comments, and newlines. This is where most edits fail.
Why Models Get It Wrong
- Tab vs. spaces: The model outputs spaces, but the file uses tabs
- Trailing whitespace: Invisible spaces at line ends
- Hallucinated code: The model "remembers" code that's slightly different
- Stale context: The file changed since the model read it
Who Uses It
Cline (primary tool), Aider (diff format), OpenCode (EditTool), Grok CLI (str_replace_editor)
3. Unified Diff / Patch Format
Very Token Efficient Specialized ModelsWhat It Does
Uses standard diff format (or custom variations) to describe changes. Lines starting with - are removed, lines with + are added, and unchanged context lines help locate the edit.
Standard Unified Diff
--- app.py
+++ app.py
@@ -10,7 +10,7 @@
def calculate_total(items):
- return sum(item.price for item in items)
+ subtotal = sum(item.price for item in items)
+ tax = subtotal * 0.1
+ return subtotal + tax
def main():
Codex's Custom Patch Format
Codex/Claude Code uses a custom patch syntax with special markers for multi-file operations:
*** Begin Patch
*** Add File: src/new_module.py
+"""New module docstring"""
+
+def new_function():
+ pass
*** Update File: src/app.py
@@ def calculate_total():
- return sum(prices)
+ subtotal = sum(prices)
+ return subtotal * 1.1
*** Delete File: src/deprecated.py
*** End Patch
Why Custom Formats?
Advantages
- Extremely token efficient
- Multiple files in one patch
- Clear operation semantics (add/update/delete)
- Familiar to developers (like git)
Challenges
- High cognitive load for generic LLMs
- Easy to hallucinate line numbers
- Context lines must match exactly
- Best with models trained on diffs
Who Uses It
Codex/Claude Code (custom patch format, primary method), Aider (standard udiff, optional format), Cline (V4A format for GPT-5 models only)
4. Line-Based / Anchor Matching
Fallback Strategy Some False Positive RiskWhat It Does
When exact matching fails, use the first and last lines of a code block as "anchors" to locate the target. The middle content is verified with fuzzy matching or similarity scoring.
How It Works
function applyAnchorEdit(fileLines, searchLines, replaceLines) {
const startAnchor = searchLines[0].trim();
const endAnchor = searchLines[searchLines.length - 1].trim();
// Find start line index in real file
const startIndex = fileLines.findIndex(line =>
line.trim() === startAnchor
);
// Find end line index (after start index)
const endIndex = fileLines.findIndex((line, idx) =>
idx > startIndex && line.trim() === endAnchor
);
if (startIndex !== -1 && endIndex !== -1) {
// Optionally: verify middle content similarity
if (similarityScore(middle) > 0.5) {
return [
...fileLines.slice(0, startIndex),
...replaceLines,
...fileLines.slice(endIndex + 1)
].join("\n");
}
}
throw new Error("Anchors not found or content mismatch");
}
Why This Helps
AI models often get the first and last lines of a function/block correct but hallucinate minor differences in the middle (comments, whitespace, formatting). Anchor matching says: "If the boundaries match and the size is right, it's probably the target."
The Risk
False Positives
If the first and last lines aren't unique (e.g., multiple functions starting with def process():), you might edit the wrong block. Agents mitigate this with similarity thresholds (OpenCode uses 50%) and size checks.
Who Uses It
Cline (Tier 3 fallback: "Block Anchor Fallback Match"), OpenCode (BlockAnchorReplacer + ContextAwareReplacer)
5. Multi-Edit / Atomic Operations
Efficient I/O All-or-NothingWhat It Does
Apply multiple disjoint edits to a single file in one atomic operation. Read the file once, apply all changes in memory (handling offset shifts), write once.
Example (OpenCode's MultiEditTool)
MultiEditTool({
filePath: "/abs/path/to/file.ts",
edits: [
{
oldString: "const API_URL = 'http://localhost'",
newString: "const API_URL = process.env.API_URL"
},
{
oldString: "console.log('debug')",
newString: "logger.debug('request received')"
},
{
oldString: "// TODO: add auth",
newString: "validateToken(req.headers.authorization)"
}
]
})
Why Use Multi-Edit?
- Performance: One file read + one file write instead of N operations
- Atomicity: All edits succeed or none do (easier to rollback)
- Offset handling: The tool manages line number shifts as edits are applied
- Common use case: Renaming variables, updating imports, bulk find-replace
Complexity
Multi-edit tools must handle overlapping edits (what if two edits affect the same lines?) and offset shifts (if edit 1 adds 3 lines, edit 2's target line is now 3 lines later). OpenCode applies edits sequentially in memory to handle this.
Who Uses It
OpenCode (dedicated MultiEditTool), Cline (multiple SEARCH/REPLACE blocks in one replace_in_file call)
Summary: Choosing the Right Method
| Scenario | Recommended Method | Why |
|---|---|---|
| Creating a new file | Whole File | No existing content to match |
| Changing one function | Search & Replace | Precise, low token cost |
| Multi-file refactor | Unified Diff / Patch | Token efficient, multiple files at once |
| Exact match failing | Anchor Matching | Handles whitespace/formatting differences |
| Renaming variable everywhere | Multi-Edit | Atomic, handles offset shifts |
| Everything else failed | Whole File | Nuclear option, always works |