Safe Editing Protocol (PROTOCOLO BLINDADO)
Safe Editing Protocol — PROTOCOLO BLINDADO (v4.5.x)
Section titled “Safe Editing Protocol — PROTOCOLO BLINDADO (v4.5.x)”For: Claude Code, Claude Desktop, and developers Version: 4.5.29 | Last updated: 2026-07-11
This is the v4.5.x evolution of the original PROTOCOLO BLINDADO from v3.x. The eight rules below incorporate the rewrite guard (v4.5.10), post-edit hash chaining (v4.5.15), structured edit responses (v4.5.15), automatic OCC (v4.5.17), and step-through undo recovery (v4.3.8, still the recommended recovery path).
If you remember only one thing: REGLA 0 first, REGLA 5 last.
REGLA 0 — Anti-rewrite guard (v4.5.10+)
Section titled “REGLA 0 — Anti-rewrite guard (v4.5.10+)”edit_file in mode replace does exact text substitution. It does
not rewrite a file — it swaps the matched block and leaves the rest
intact. A documented 2026-06-11 incident: old_text was a 15-line header
and new_text was the full 150-line file body. The edit returned success,
but the file had 298 lines with the procedure duplicated at the bottom —
the model intended a rewrite, the tool did a partial replace, and the bug
was silent.
v4.5.10 added a server-side guard that blocks this anti-pattern. The guard fires when all three signals match:
len(new_text) > 2 × len(old_text)(size ratio)- The file has more than 50% of its content outside the matched block
len(new_text) > 500 bytesand> 50%of file size
When it fires, the edit is rejected with:
“looks like an accidental full-file rewrite; use write_file instead. Pass allow_rewrite:true to override (creates a safety backup).”
Use write_file for whole-file rewrites. Override only when you are
sure the edit is correct (allow_rewrite:true creates a safety backup).
The force flag does not bypass this guard (decoupled in v4.5.14).
Use allow_rewrite for that.
REGLA 1 — Verification before edit
Section titled “REGLA 1 — Verification before edit”Before any edit, verify the exact content you intend to change:
// 1. Read the file (range read = token-efficient)read_file({path: "file.cs", start_line: 10, end_line: 30})
// 2. Confirm the text exists (count check)search_files({path: "file.cs", pattern: "exact text from read", count_only: true})// → must return > 0
// 3. (Optional) Dry-run the edit / dry-run an analyzeanalyze_operation({path: "file.cs", operation: "edit", old_text: "...", new_text: "..."})search_files without include_content:true is a filename search
(v4.5.24 fix). If you want to grep file contents, pass include_content:true
or use any of output_format:json / output:content / context_lines:N
(all auto-flip include_content).
REGLA 2 — Capture literal text (never retype)
Section titled “REGLA 2 — Capture literal text (never retype)”Copy the old_text exactly from the read result. Do not retype it
from memory. In particular watch out for:
- Tabs vs spaces (model sees spaces, file has tabs → no match)
- CRLF vs LF (Windows-edited files may have
\r\n) - Trailing whitespace (model strips it, file has it → no match)
If your old_text keeps not matching, try tolerant_whitespace:true
(v4.5.7+). It treats one tab as 4 spaces and CRLF/CR as LF, while
preserving the file’s original bytes verbatim outside the match region.
Conservative — does not normalize runs of multiple spaces.
// Mixed tabs/spaces in the file? Try tolerant matching:edit_file({ path: "file.js", old_text: " taula_llistat(", new_text: " taula_llistat_new(", tolerant_whitespace: true})REGLA 3 — Same file, multiple changes → multi_edit
Section titled “REGLA 3 — Same file, multiple changes → multi_edit”A successful edit_file invalidates the file cache and updates the
session’s OCC baseline (so a follow-up edit_file immediately works —
no re-read needed). But it still does N round-trips and N backups.
For 2+ edits in the same file, prefer multi_edit:
multi_edit({ path: "file.go", edits_json: "[ {\"old_text\":\"foo()\", \"new_text\":\"bar()\"}, {\"old_text\":\"baz()\", \"new_text\":\"qux()\"} ]", expected_hash: "1a2b3c4d" // optional — same OCC semantics as edit_file})For edits across many files, prefer batch_operations({request_json})
with atomic:true. For complex multi-step refactors,
batch_operations({pipeline_json: "..."}) with parallel:true runs
independent steps concurrently via the DAG scheduler.
REGLA 4 — OCC via expected_hash (v4.5.6+) and --auto-occ (v4.5.17+)
Section titled “REGLA 4 — OCC via expected_hash (v4.5.6+) and --auto-occ (v4.5.17+)”Two complementary mechanisms protect against stale reads:
Explicit OCC — pass expected_hash
Section titled “Explicit OCC — pass expected_hash”// First edit: read returns content_hash (now in StructuredContent)read_file({path: "file.go"})// → structuredContent.content_hash: "1a2b3c4d"// → text body (no trailer)
// Second edit: pass the previous content_hash as expected_hashedit_file({ path: "file.go", old_text: "foo()", new_text: "bar()", expected_hash: "1a2b3c4d"})// On mismatch:// "stale edit: file content changed since read// (expected hash: 1a2b3c4d, actual: 9z8y7x6w).// Re-read the file with read_file to get the current content_hash, then retry."multi_edit also accepts expected_hash (v4.5.13+). The check happens
once before the atomic loop, so a stale hash never creates a backup and
never applies any edits.
Automatic OCC — --auto-occ CLI flag
Section titled “Automatic OCC — --auto-occ CLI flag”The session remembers the content_hash of every file it last read or
last wrote. Before an edit without explicit expected_hash, the engine
compares on-disk hash to that baseline. If something else modified the
file, it fires.
Modes (set once at startup):
| Mode | Behavior when on-disk hash differs from session baseline |
|---|---|
off | No check |
warn | Append a non-blocking notice to the edit response |
block | Reject the edit with a clear error |
warn is the default. Critical correctness property: the baseline is
updated on the session’s own writes (using the post-edit
content_hash from v4.5.15), so consecutive edits never false-positive.
v4.5.26 —
write_filenow refreshes the OCC baseline too. Until v4.5.25,write_filedid not callRecordWriteHash, so awrite → editchain in the same session would trip a falseexternal_changewarning on the very next edit (the write was recorded as a “read of the prior content” but not as a write of the new content).write_filenow records its post-writecontent_hashimmediately, so chainingwrite_file(path, content)→edit_file(path, old_text, new_text, expected_hash: <hash from write>)works without spurious OCC notices. See Structured Output for the new typedcontent_hashfield on thewrite_fileresponse.
REGLA 5 — Mode selection table
Section titled “REGLA 5 — Mode selection table”Pick the right edit_file mode (or a different tool) for the situation:
| Situation | Use |
|---|---|
| One exact text replacement (single occurrence) | edit_file (default mode:"replace") |
| One exact text, but file has it N times | edit_file({old_text, new_text, occurrence: N}) (1=first, -1=last) |
| Global literal find/replace | edit_file({mode:"search_replace", pattern, replacement}) |
| Regex with capture groups | edit_file({mode:"regex", patterns_json}) |
| Delete a known line range (e.g. lines 45-60) | edit_file({mode:"delete_range", start_line, end_line}) |
| Replace a known line range | edit_file({mode:"replace_range", start_line, end_line, new_text}) |
| Whole-file rewrite (small file, < 5 KB) | write_file (NOT edit_file) |
| Multi-edit same file | multi_edit |
| Project-wide find/replace | project_replace |
| Cross-file atomic edits | batch_operations({request_json}) |
| Multi-step pipeline with DAG parallelism | batch_operations({pipeline_json}) |
| Delete a single file | delete_file (soft — recoverable via SD-ID) |
| Cross-file delete with confirmation | analyze_operation({operation:"delete"}) first |
Pre-flight check before any global replace:
search_files({pattern, count_only:true}) to verify the impact.
diff_format (v4.5.14+) controls the diff verbosity:
"" / "auto" (default: full when small, summary past 200 diff lines),
"full", "summary" (per-hunk ranges + 3 anchor lines),
"stat" (+N -M), "none". The same parameter works on multi_edit
(v4.5.25 — aggregate diff of the whole batch).
REGLA 6 — Dry-run before destructive ops
Section titled “REGLA 6 — Dry-run before destructive ops”| Op | Dry-run call |
|---|---|
| Whole-file delete | analyze_operation({path, operation:"delete"}) |
| Whole-file write | analyze_operation({path, operation:"write"}) |
| Single edit | edit_file({..., dry_run:true}) (regex / search_replace) |
| Pipeline execution | batch_operations({pipeline_json: "...", dry_run:true}) |
| Project-wide replace | project_replace({path, find, replace, preview:true}) |
| WSL sync | wsl({action:"sync", direction:"...", dry_run:true}) |
| Minify JS | minify_js({path, dry_run:true}) |
analyze_operation returns risk level (LOW/MEDIUM/HIGH/CRITICAL), change
percentage, and impact preview — no file is touched.
REGLA 7 — Backup recovery (step-through undo)
Section titled “REGLA 7 — Backup recovery (step-through undo)”Every successful edit creates a backup linked into a per-file chain via
PreviousBackupID. Compact responses show a truncated ID for display:
M file.go | 7@+7-0 | 42L | UNDO:20260501-123650 | chain:20260501-123649The chain is your rollback path. Step-through is preferred over a full restore because it lets you undo one edit at a time:
// Undo the most recent edit on a filebackup({action: "undo_last", file_path: "file.go"})
// Preview what would be undone (no write)backup({action: "undo_last", file_path: "file.go", preview: true})
// Walk further back the chainbackup({action: "undo_last", file_path: "file.go"}) // call again
// See the full chainbackup({action: "undo_chain", file_path: "file.go"})
// Restore a specific backup by full IDbackup({ action: "restore", backup_id: "20260501-123650-333c964cc3af7a82", // full ID, not truncated file_path: "file.go"})
// Find backups for a specific filebackup({action: "list", filter_path: "file.go"})Soft-delete recovery (v4.5.11+) — every delete_file (soft, default)
returns a Soft-Delete ID (SD-ID). Restore via the same backup tool:
// Discoverbackup({action: "list_trash", filter_path: "file.go"})
// Restorebackup({action: "restore_trash", sd_id: "sd-20260611-150455-a1b2c3d4"})
// Cleanup oldbackup({action: "purge_trash", older_than_days: 7, dry_run: true})HIGH and CRITICAL edits auto-verify file integrity after writing
(readable? reasonable size? line count? CRC32 hash). If the file is
truncated or corrupted, the response reports it immediately.
REGLA 8 — Complete pre-flight checklist
Section titled “REGLA 8 — Complete pre-flight checklist”## Before editing file: <FILENAME>
### STEP 1 — Read & verify- [ ] read_file({path, start_line, end_line}) and copy EXACT bytes- [ ] Note line numbers- [ ] (Optional) analyze_operation({operation:"edit", ...}) for risk
### STEP 2 — Confirm pattern- [ ] search_files({pattern, count_only:true})- [ ] Expected vs actual count match
### STEP 3 — Choose mode- [ ] Whole file → write_file (or REGLA 0 will block)- [ ] Single surgical change → edit_file (default mode)- [ ] Multiple changes → multi_edit- [ ] Line range delete → edit_file({mode:"delete_range", start_line, end_line})- [ ] Cross-file atomic → batch_operations({request_json, atomic:true})
### STEP 4 — OCC + safety- [ ] Pass expected_hash from previous read/edit (or rely on --auto-occ=warn)- [ ] High/critical risk: pass force:true; review VERIFY instruction in response
### STEP 5 — Execute- [ ] edit_file / multi_edit / write_file / batch_operations call- [ ] Capture the UNDO: ID and chain: parent ID from the response
### STEP 6 — Verify- [ ] search_files({pattern: old_text, count_only:true}) → 0 matches- [ ] read_file({path, start_line, end_line}) to confirm resultCommon errors
Section titled “Common errors”“old_text not found in current file”
Section titled ““old_text not found in current file””- File changed since your read → re-read it
- Whitespace mismatch → try
tolerant_whitespace:true - Multi-occurrence but you expected one → either provide enough context
to make it unique, or pass
occurrence: N
“stale edit: file content changed since read”
Section titled ““stale edit: file content changed since read””- OCC detected a hash mismatch. Re-read and use the new
StructuredContent["content_hash"]as yourexpected_hash. - Or relax to
warnmode via--auto-occ=warn(default) — it appends a notice instead of blocking.
“looks like an accidental full-file rewrite”
Section titled ““looks like an accidental full-file rewrite””- REGLA 0 fired. Use
write_filefor whole-file rewrites. - If the rewrite is genuinely intended, pass
allow_rewrite:true.
“OPERATION BLOCKED — HIGH/CRITICAL risk”
Section titled ““OPERATION BLOCKED — HIGH/CRITICAL risk””- v3.x behaviour. In v4.5.x, HIGH/CRITICAL proceed with a backup
and a
VERIFYinstruction.force:trueis no longer required for them; it is reserved for the risk-threshold bypass.
“Destructive git operation ‘restore’ requires force=true”
Section titled ““Destructive git operation ‘restore’ requires force=true””- For
git(action:"restore", ...)against the working tree (nostaged:true, nodry_run:true), the engine requiresforce:true. For unstage (staged:true) and preview (dry_run:true),forceis not needed.
Disaster recovery
Section titled “Disaster recovery”If anything goes wrong, the recovery order is:
- Stop editing. More edits can mask the original state.
- List the chain:
backup({action:"undo_chain", file_path:"file.go"}) - Preview the undo:
backup({action:"undo_last", file_path:"file.go", preview:true}) - Step back:
backup({action:"undo_last", file_path:"file.go"}) - For soft-deletes:
backup({action:"list_trash", filter_path:"file.go"})thenbackup({action:"restore_trash", sd_id:"..."}) - Full recovery guide at runtime:
server_info({action:"help", topic:"recovery"})orhelp({topic:"recovery"}).
Related
Section titled “Related”- Claude instructions — tool inventory + workflow
- Efficient editing — token-saving patterns
- Project-wide replace — bulk refactor tool
- Core Tools — full tool matrix
Version: v4.5.29 | Last updated: 2026-07-11