Safe Editing Protocol
Safe Editing Protocol
Section titled “Safe Editing Protocol”For: Claude Code, Claude Desktop, and developers Last Updated: February 2026
Quick Start Checklist
Section titled “Quick Start Checklist”Use this checklist for ANY file edit operation:
## Before Editing File: [FILENAME]
### STEP 1: Read & Verify (5 min)- [ ] Open file with `read_file() with start_line/end_line` to see exact content- [ ] Copy the EXACT text you want to change (including spaces/tabs)- [ ] Note the line numbers- [ ] Paste here: [EXACT_TEXT_HERE]
### STEP 2: Confirm Pattern (2 min)- [ ] Run `search_files() with count_only:true` to find how many times it appears- [ ] Expected count: [NUMBER]- [ ] Actual count: [NUMBER]- [ ] ✅ Counts match: YES / NO
### STEP 3: Validate Edit (2 min)- [ ] Small file (< 1 MB): Use `edit_file()` or `edit_file()`- [ ] Large file (> 1 MB): Use `batch_operations` with atomic=true- [ ] Critical change: Use `batch_operations` even if small- [ ] Multiple changes: ALWAYS use `batch_operations`- [ ] ✅ Method chosen: [METHOD]
### STEP 4: Execute Edit (1 min)- [ ] For recovery_edit:path: [FILE_PATH] old_text: [COPY_FROM_READ_FILE_RANGE] new_text: [REPLACEMENT_TEXT] force: false (unless risk warning)
- [ ] For batch_operations:```json{ "operations": [ { "type": "edit", "path": "[FILE_PATH]", "old_text": "[FROM_READ_FILE_RANGE]", "new_text": "[REPLACEMENT]" } ], "atomic": true}- ✅ Operation executed
STEP 5: Verify Result (2 min)
Section titled “STEP 5: Verify Result (2 min)”- File size didn’t change drastically
- New text appears in file: Use
read_file() with start_line/end_lineto check - Old text is gone: Use
search_files() with count_only:trueto confirm 0 matches - ✅ All verifications passed: YES / NO
Total Time: ~12 minutes for safe edit
---
## Rules
### Rule 1: Do Not Use Fuzzy Matching for Critical Edits**Avoid:** Copying text from memory and hoping it matches.**Recommended:** Use `read_file() with start_line/end_line` to get exact text.
### Rule 2: Do Not Skip Line Ending Verification**Avoid:** Assuming the file uses Unix line endings.**Recommended:** Check `read_file() with start_line/end_line` output for `\r\n` or `\n`.
### Rule 3: Do Not Edit Without Backup**Avoid:** Using `edit_file()` without `force=true` when at risk.**Recommended:** Always let the server create backups automatically (v3.8.0+).
### Rule 4: Do Not Make Multiple Changes in One Edit**Avoid:**old_text: “line1\nline2\nline3\nline4\nline5” new_text: “line1_new\nline2_new”
**Recommended:**operations: [ { type: “edit”, old_text: “line1”, new_text: “line1_new” }, { type: “edit”, old_text: “line2”, new_text: “line2_new” }, { type: “edit”, old_text: “line3”, new_text: "" } ]
### Rule 5: Do Not Skip Verification After Edit**Avoid:** Moving on immediately after an edit completes.**Recommended:** Verify with `search_files() with count_only:true` that the old text is gone.
---
## Decision TreeSTART: “I need to edit a file” │ ├─ Is it a CRITICAL file? (config, code, database schema) │ ├─ YES → Use batch_operations with atomic=true │ └─ NO ─┐ │ └─ Go to next check │ ├─ Does the old_text span MULTIPLE LINES? │ ├─ YES (3+ lines) → Use batch_operations │ └─ NO (1-2 lines) ─┐ │ └─ Go to next check │ ├─ Are you making MULTIPLE CHANGES? │ ├─ YES (2+ edits) → Use batch_operations │ └─ NO (1 edit) ─┐ │ └─ Go to next check │ ├─ Is the FILE SIZE > 1 MB? │ ├─ YES → Use batch_operations │ └─ NO ─┐ │ └─ Go to next check │ └─ DECISION: Use edit_file() or edit_file()
---
## Tool Reference
### 1. read_file() with start_line/end_line - Get Exact Content```pythonresponse = await client.call_tool( "filesystem-ultra:read_file", { "path": "/path/to/file.cs", "start_line": 10, "end_line": 20 })# Output shows EXACT text with line numbers# COPY THIS EXACTLY for old_text parameterWhy: Guarantees you have the exact text including whitespace
2. search_files() with count_only:true - Verify Pattern Exists
Section titled “2. search_files() with count_only:true - Verify Pattern Exists”response = await client.call_tool( "filesystem-ultra:search_files", { "path": "/path/to/file.cs", "pattern": "exact_text_from_read_file", "count_only": true })# Output shows how many matches and which lines# Must be > 0 before attempting editWhy: Confirms the text exists exactly as you expect
3. edit_file() - Single Safe Edit
Section titled “3. edit_file() - Single Safe Edit”response = await client.call_tool( "filesystem-ultra:edit_file", { "path": "/path/to/file.cs", "old_text": "COPY_EXACTLY_FROM_read_file", "new_text": "replacement text", "force": False # Set to True only if risk warning appears })# Returns: Modified file, backup ID, lines affected# After: Verify with search_files() with count_only:true that old text is goneWhen to use: Single-line or 1-2 line replacements on small files When NOT to use: Multiple changes, large edits, critical files
4. batch_operations() - Multiple Safe Edits
Section titled “4. batch_operations() - Multiple Safe Edits”response = await client.call_tool( "filesystem-ultra:batch_operations", { "operations": [ { "type": "edit", "path": "/path/to/file.cs", "old_text": "line1 to replace", "new_text": "line1 replacement" }, { "type": "edit", "path": "/path/to/file.cs", "old_text": "line2 to replace", "new_text": "line2 replacement" } ], "atomic": True # ALWAYS true for safety })# All operations apply together, or none if any fails# After: Verify each change with search_files() with count_only:trueWhen to use: ALWAYS for critical edits, multiple changes, large files Advantage: Atomic (all-or-nothing), automatic backup
5. search_files() - Find Before Editing
Section titled “5. search_files() - Find Before Editing”response = await client.call_tool( "filesystem-ultra:search_files", { "path": "/path/to/file.cs", "pattern": "partial_text_to_find", "context_lines": 3 })# Shows matches with surrounding code# Use to understand the context before editingWhy: Understand code context before making changes
Complete Workflow Example
Section titled “Complete Workflow Example”Scenario: Edit C# File (from Bug #8)
Section titled “Scenario: Edit C# File (from Bug #8)”Before (without validation):
# Read fileresponse1 = client.call_tool("read_file", {"path": "file.cs"})
# Try to edit without checking if text existsresponse2 = client.call_tool("edit_file", { "path": "file.cs", "old_text": "...some multiline text...", "new_text": "replacement"})# ❌ FAILS: "old_text not found"After (with validation):
# STEP 1: Read exact contentresponse1 = await client.call_tool( "filesystem-ultra:read_file", {"path": "file.cs", "start_line": 10, "end_line": 20})# Output:# Line 10: // Orders list# Line 11: public List<C1Pedidos> Orders { get; set; } = new();# Line 12: private List<C1Pedidos> filteredOrders = new();# Line 13: private bool isFiltered = false;# Line 14: private Dictionary<int, bool> listadoValidados = new Dictionary<int, bool>();
# STEP 2: Copy EXACTLY and verify it existsexact_text = """ // Orders list public List<C1Pedidos> Orders { get; set; } = new(); private List<C1Pedidos> filteredOrders = new(); private bool isFiltered = false; private Dictionary<int, bool> listadoValidados = new Dictionary<int, bool>();"""
response2 = await client.call_tool( "filesystem-ultra:search_files", {"path": "file.cs", "pattern": exact_text, "count_only": true})# Output: Found 1 match at line 10# ✅ Confirmed: Text exists exactly as expected
# STEP 3: Use batch_operations (safer for multiline)response3 = await client.call_tool( "filesystem-ultra:batch_operations", { "operations": [ { "type": "edit", "path": "file.cs", "old_text": exact_text, "new_text": """ // Orders list public List<C1Pedidos> Orders { get; set; } = new(); private List<C1Pedidos> filteredOrders = new(); private bool isFiltered = false;""" } ], "atomic": True })# ✅ SUCCESS: 1 replacement, file updated
# STEP 4: Verify the resultresponse4 = await client.call_tool( "filesystem-ultra:search_files", {"path": "file.cs", "pattern": "private Dictionary<int, bool>", "count_only": true})# Output: Found 0 matches# ✅ Confirmed: Old text is gone
response5 = await client.call_tool( "filesystem-ultra:read_file", {"path": "file.cs", "start_line": 10, "end_line": 20})# Output shows updated content without the Dictionary line# ✅ SUCCESS: Edit verifiedCommon Mistakes
Section titled “Common Mistakes”Mistake 1: Different Line Endings
Section titled “Mistake 1: Different Line Endings”❌ BAD:old_text = "line1\nline2" # Unix# But file has: "line1\r\nline2" # Windows# Result: NO MATCH
✅ GOOD:# Check read_file() with start_line/end_line output for line ending type# Match exactly what's shownMistake 2: Extra Whitespace
Section titled “Mistake 2: Extra Whitespace”❌ BAD:old_text = "public List Orders { get; set; }"# But file has: " public List Orders { get; set; }"# (4 spaces at start)# Result: NO MATCH
✅ GOOD:old_text = " public List Orders { get; set; }"# Copy from read_file() with start_line/end_line output with indentationMistake 3: TAB vs SPACES
Section titled “Mistake 3: TAB vs SPACES”❌ BAD:old_text = " property = value" # 4 spaces# But file has: "\tproperty = value" # 1 tab# Result: NO MATCH
✅ GOOD:# Use read_file() with start_line/end_line which shows [SPACE] and [TAB] clearlyMistake 4: Editing Without Verification
Section titled “Mistake 4: Editing Without Verification”❌ BAD:# Edit multiple lines and hope it works# No verification afterwards
✅ GOOD:# After each edit, use search_files() with count_only:true on old text# Should return 0# Use read_file() with start_line/end_line to view the actual resultMistake 5: One Big Edit Instead of Smaller Ones
Section titled “Mistake 5: One Big Edit Instead of Smaller Ones”❌ BAD:old_text = """entire_method_body_here lots_of_lines might_fail"""new_text = "simplified_replacement"
✅ GOOD:# Split into multiple edits if possible# Use batch_operations for all changes# One operation per logical changeTroubleshooting
Section titled “Troubleshooting””old_text not found in current file”
Section titled “”old_text not found in current file””Diagnosis: The exact text doesn’t exist
Steps:
- Run
read_file() with start_line/end_linearound the area you think it is - Copy the text EXACTLY (spaces, tabs, line endings)
- Use
search_files() with count_only:truefirst to verify it exists - If found, try again with exact text
”Context validation failed”
Section titled “”Context validation failed””Diagnosis: File was modified since you read it
Steps:
- Run
read_file() with start_line/end_lineagain to get latest content - Verify the text still exists
- Update your
old_textif needed - Retry edit
”OPERATION BLOCKED - Change impact is HIGH/CRITICAL”
Section titled “”OPERATION BLOCKED - Change impact is HIGH/CRITICAL””Diagnosis: Edit is large/risky, need explicit approval
Steps:
- Review the warning message carefully
- Add
"force": trueif you’re sure - Better: Use
batch_operationswith smaller edits - Better: Create backup first manually if needed
”Expected recovery but file content changed unexpectedly”
Section titled “”Expected recovery but file content changed unexpectedly””Diagnosis: Something went wrong with file sync
Steps:
- Don’t edit anymore
- Check backup files in
.mcp-filesystem-backups/ - Use
backupwith action “restore” if needed - Report this as a bug
When to Use Which Tool
Section titled “When to Use Which Tool”| Situation | Tool | Reason |
|---|---|---|
| First time editing a file | read_file() with start_line/end_line | Get exact content |
| About to edit | search_files() with count_only:true | Confirm text exists |
| Single line edit, small file | edit_file() | Simple, fast |
| Multiple line edit | batch_operations | Atomic, safer |
| Critical file | batch_operations | More reliable |
| File > 1 MB | batch_operations | Better performance |
| Don’t know where to edit | search_files() | Find first |
| Want to see context | search_files() | Context included |
| After any edit | search_files() with count_only:true | Verify success |
| Verify entire section | read_file() with start_line/end_line | See actual result |
Emergency Procedures
Section titled “Emergency Procedures”If File Got Corrupted
Section titled “If File Got Corrupted”# 1. Stop immediately - don't make more edits
# 2. Check if backup existsresponse = await client.call_tool( "filesystem-ultra:backup", {"action": "list", "file_path": "/path/to/file.cs"})
# 3. Restore from backupresponse = await client.call_tool( "filesystem-ultra:backup", { "action": "restore", "backup_id": "backup_id_from_list", "restore_to": "/path/to/file.cs" })
# 4. Verify restorationresponse = await client.call_tool( "filesystem-ultra:read_file", {"path": "/path/to/file.cs", "start_line": 1, "end_line": 20})If You’re Not Sure About an Edit
Section titled “If You’re Not Sure About an Edit”1. DON'T USE edit_file()2. DON'T USE batch_operations without verification3. DO USE: read_file() with start_line/end_line to see what's there4. DO USE: search_files() with count_only:true to confirm text exists5. DO USE: search_files() to understand context6. THEN decide if edit is safeRelated Documentation
Section titled “Related Documentation”- Bug #8 Fix Details - Complete technical explanation
- Edit Operations Source - Implementation details
- Backup & Recovery System - How backup works
- Windows Filesystem Issues - Platform-specific
Summary
Section titled “Summary”The 5 steps:
- Read - Use
read_file() with start_line/end_lineto see exact content - Verify - Use
search_files() with count_only:trueto confirm pattern exists - Choose - Use
edit_file()orbatch_operations - Execute - Run the edit with exact text
- Confirm - Use
search_files() with count_only:trueto verify success
The 5 rules:
- Do not use fuzzy matching for critical edits.
- Do not skip line ending verification.
- Do not edit without backup.
- Do not make multiple changes in one edit.
- Do not skip verification after editing.
Updated: February 2026 | Version: 3.13.2