[Bug] Claude does not verify file edits applied correctly — blind edit-and-proceed
Blind File Edits — No Post-Edit Verification
Phase: Execution (Phase 3 in the failure chain documented in #32650)
Description
When Claude uses file editing tools (string replacement, line-targeted edits, regex-based modifications) to apply code changes, it does not read the file back afterward to verify the edit applied correctly. It assumes the tool succeeded and proceeds to the next step or reports completion.
Failure modes include:
- Target block not found — the edit tool silently fails to match (e.g., whitespace differences, the code was already modified, or the match string is ambiguous). Claude doesn't notice the file is unchanged.
- Wrong block matched — in files with similar patterns, the edit matches an unintended location. Claude doesn't verify which occurrence was modified.
- Partial application — a multi-part edit where some replacements succeed and others don't. Claude reports the full edit as complete.
Expected Behavior
After applying a file edit, Claude should:
- Read back the modified section of the file
- Verify the intended change is present
- Verify no unintended changes were introduced (especially for regex-based edits)
- Only then proceed to the next step or report success
This is the file-editing equivalent of the "per-step verification gate" described in #32293 — each edit is a step that should be verified before proceeding.
Why This Is Distinct
- #32289 covers generating incorrect code (the artifact itself is wrong)
- #32293 covers lack of verification between sequential steps (like SQL files)
- This issue covers lack of verification of the edit operation itself — the code may be correct but the application may have failed silently
Impact
In a large codebase (~2M LOC), files often contain similar patterns (boilerplate, repeated structures, template instantiations). A regex or string replacement intended for one function can silently match another. Without read-back verification, these misapplied edits become latent bugs that surface much later — often in a completely different session, making them extremely difficult to trace back to the faulty edit.
Related Issues
- #32293 — No per-step verification gates
- #32289 — Generates incorrect code and reports complete
- #32295 — Silently skips verification steps
- #32650 — Meta-issue (Phase 3 addition)
Environment
- Claude Code 2.1.71
- Windows 11
- Large C++ codebase (~2M LOC)
13 Comments
I've submitted a plugin to help with this in PR #32755. It's a PostToolUse hook that reads files back after Edit operations and warns Claude if the expected new content isn't found.
@mvanhorn — just reviewed PR #32755 in detail. Clean implementation — the PostToolUse hook pattern is exactly the right approach, and the design choices are solid:
A few thoughts from our experience (we've been dealing with this failure mode across 100+ sessions):
1. The 5-char threshold might miss some failures. We've seen edits fail on short but critical strings — single-line changes like
true→false, or a version number bump8.0→8.1. These are under 5 chars but a failed edit would be catastrophic. Would you consider making the threshold configurable (env var or plugin config)?2. Consider also checking that
old_stringis absent. Ifnew_stringis found butold_stringis also still present, that could indicate the edit was applied to the wrong occurrence (or thatreplace_allwas intended but not set). This would catch the "edit applied to wrong location" variant of the bug.3. Encoding edge case on Windows. We run Claude Code on Windows 11. The
open(file_path, "r")call uses the system default encoding, which on Windows is oftencp1252, not UTF-8. If the file contains UTF-8 content (very common in codebases), the read-back comparison could fail even when the edit succeeded. Consideropen(file_path, "r", encoding="utf-8")with a fallback.We'll be installing this locally regardless of merge status — it directly addresses a failure mode we've documented across dozens of sessions. Thank you for turning a bug report into a working fix.
Community Cross-Reference
Blind file edits — mutations applied without read-back verification — has community confirmation:
| Issue | 👍 | Comments | Key Detail |
|-------|:--:|:--------:|-----------|
| #5178 | 6 | 5 | Direct match — "Edit tool reports false success and shows simulated content without actually modifying files" |
| #12462 | 10 | 13 | "File has been unexpectedly modified" when file is NOT modified — Edit tool producing false signals |
#5178 is a textbook case: the Edit tool claims success and Claude even shows the modified content, but the actual file on disk is unchanged. This is exactly why post-edit read-back verification is necessary.
Update: @mvanhorn submitted PR #32755 implementing a PostToolUse hook to catch this. We've deployed an enhanced version locally (added configurable threshold, old_string-still-present detection, and Windows UTF-8 encoding chain). Details in our earlier comment on this issue.
Community Validation Update — Blind File Edits
This issue has the most concrete technical reproduction reports — the "File unexpectedly modified" error is one of the most-reported bugs in the repo with at least 10 dedicated issues.
GitHub Reports (8 "unexpectedly modified" issues)
| Issue | Platform | Title |
|-------|----------|-------|
| #13456 | Cross-platform | Edit tool fails on files with CRLF line endings |
| #12805 | Windows (MINGW) | Edit/Write tools fail with 'unexpectedly modified' |
| #7443 | General | Edit tool fails with "unexpectedly modified" (critical — cannot code) |
| #10882 | VSCode | "Unexpectedly modified" errors break Edit tool in VSCode extension |
| #7918 | Windows | File Edit Fails on Windows with Unexpected Modification Error |
| #17684 | Windows | Edit tool fails with "unexpectedly modified" when file hasn't changed |
| #5926 | General | Frequent "Error editing file" on Update |
| #19699 | General | Claude gets stuck in infinite loop repeating the same failing command |
Medium
Pattern Analysis
Two distinct failure modes are being conflated in these reports:
PR #32755 (edit-verifier PostToolUse hook) directly addresses failure mode #2 by reading the file back after every edit and verifying the new content is present and the old content is gone. We've deployed an enhanced version locally with 3 improvements: configurable threshold, old_string-gone check, and Windows encoding fallback.
Failure mode #1 appears to be a platform-specific bug in the Edit tool's change detection, likely related to line ending normalization. This needs a fix in the tool itself, not just a verification hook.
Part of the completion-integrity taxonomy tracked in #32650.
Thanks for the detailed review - glad the design choices make sense, especially the non-blocking approach.
@mvanhorn — we've had your enhanced version running locally for a couple days now and it's already caught 2 legitimate failures that would have gone unnoticed. Both were "old content still present" catches (the improvement we suggested) — cases where the Edit tool reported success but replaced the wrong occurrence of a non-unique string.
The three enhancements we added on top of your base:
EDIT_VERIFY_MIN_CHARSenv var, default 3) — caught atrue→falseedit that would have been below the original 5-char cutoffnew_stringWAS present (because the edit went somewhere), butold_stringwas also still present at the intended locationHappy to share the full source if you want to incorporate any of these into the PR. The non-blocking design was the right call — these catch real issues without halting the workflow on false positives.
Good catches from real usage - the old-string-gone check is the one I'd most want to integrate. Can you paste the diff or open a PR against my branch with those 3 changes?
Pass 5 — Final Evidence Update (Blind File Edits)
New GitHub Issues
Mid-Edit Abort (NEW failure mode)
Pass 5 identified a new variant: token exhaustion mid-edit — Claude runs out of output tokens partway through a file modification, leaving syntactically broken code with no rollback. 3 GitHub reports document this pattern. This is worse than a blind edit — it's a partial blind edit that guarantees breakage.
108-Hour Unattended Test (DEV Community)
A developer ran Claude Code unattended for 108 hours. Among the failures:
rm -rf ./src/— Claude deleted the entire source directory. The edit pipeline had no verification or rollback mechanism.Community Mitigations
Enterprise Signal
Part of the completion-integrity taxonomy tracked in #32650.
A PostToolUse hook can verify edits applied correctly:
This catches the three main failure modes:
The hook warns but doesn't block (exit 0), so Claude sees the warning and can re-read the file to verify.
For rollback capability, pair with a PreToolUse checkpoint hook that copies files before edit:
@yurukusa Thanks for sharing the hook implementation — that's a solid workaround. I've been running something similar via a PostToolUse hook that re-reads the edited file and diffs against expected content. The core issue remains that this should be default behavior, not something users have to bolt on. Every Edit tool call should include a verification read as part of the atomic operation.
@mvanhorn — apologies for the delayed loop-close here. The full merged version with all 3 enhancements (configurable threshold via
EDIT_VERIFY_MIN_CHARS, old-string-gone check, Windows encoding fallback chain) was posted as a complete file on PR #32755. Happy to open a PR against your branch if that's easier to merge than copying from the comment. Just let me know which you'd prefer.The enhanced version has been running in our environment since mid-March — it's caught 2 real edit failures (wrong-occurrence match on a common C++ pattern) and zero false positives with the threshold at 10 chars.
Still agree with the core point: this should be default runtime behavior, not a community hook. But until it is, the hook is the best we've got.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.