Edit and MultiEdit tools fail with persistent 'File has been modified since read' errors

Open 💬 21 comments Opened Jul 15, 2025 by panyaoyu

Environment

  • Platform (select one):
  • [x] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <\!-- specify -->
  • Claude CLI version: 1.0.51 (Claude Code)
  • Operating System: CentOS Linux 8
  • Terminal: xterm-256color

Bug Description

File update operations using Edit and MultiEdit tools fail with persistent "File has been modified since read" errors, even when no external modifications have occurred. The tools require re-reading files before editing, but continue to fail after re-reading.

Steps to Reproduce

  1. Create a new file using the Write tool
  2. Read the file using the Read tool to verify contents
  3. Attempt to update the file using the Edit tool with valid old_string and new_string parameters
  4. Observe the error: "File has been modified since read, either by the user or by a linter. Read it again before attempting to write it."
  5. Re-read the file and attempt the edit again
  6. The same error persists

Expected Behavior

After reading a file, the Edit and MultiEdit tools should successfully update the file content when provided with valid parameters, without requiring multiple read operations or failing due to false modification detection.

Actual Behavior

Both Edit and MultiEdit tools consistently fail with "File has been modified since read" errors, even immediately after reading the file. The tools become unusable for file updates, despite the file content appearing unchanged between reads.

Additional Context

  • This issue occurs with newly created files that have not been modified externally
  • The error suggests file modification detection is overly sensitive or incorrect
  • System reminders indicate file state tracking issues
  • Both Edit and MultiEdit tools exhibit the same behavior

View original on GitHub ↗

21 Comments

jyogesh17 · 1 year ago

I am also getting the same too frequently, tried multiple solutions but didn't work any

tinybluedev · 1 year ago

given the file 777, ensured g+w, rebooted machine, ran /doctor --- this is still present, will not write to a file.

foerster · 12 months ago

This started to happen to me all of a sudden today.
⎿ Error: File has been modified since read, either by the user or by a
linter. Read it again before attempting to write it.

cline and other tools work fine, but claude code started misbehaving and now unusable for me.

codyde · 12 months ago

Yeah im having this same issue constantly. Eventually CC ends up creating a new copy of the file, deleting the old one, and replacing it - but this is a really annoying behavior.

danhilse · 11 months ago

Also having this issue, tried killing all daemons, ide, workers, dev server, issue persists

czclaxton · 11 months ago

Also having this issue

ControlYourPotatoes · 10 months ago

I'd been having the same issue, my agent is specifying that is the linter modifying the file after he reads it. Any suggestions on how to diagnose that?

mbelot007 · 10 months ago

I am also encountering this issue along with consistent Error: String to replace not found in file. and Error: InputValidationError: MultiEdit failed due to the following issue: The parameter edits type is expected as array but provided as string``. Overall consistent issue with MultiEdit tool. It takes Claude Code multiple attempts to update the file, eventually cutting corners and quietly omit specific edit operations.

markatto · 10 months ago

I was running into this, and the root cause was files with an mtime in the future. Simply touching everything fixed it. Future mtime was caused by copying files from a utc host to a box in a negative time zone.

Dugnist · 9 months ago

This happens very often, and even when I don't touch the same file...

<img width="1107" height="828" alt="Image" src="https://github.com/user-attachments/assets/2dfa0a28-96ab-4e93-b6a1-0ee1c0dbe746" />

BenNewman100 · 8 months ago

are any of you running on windows?

I discovered an issue with bash style paths for read and edit built in tools, but it only affects windows

https://github.com/anthropics/claude-code/issues/7918#issuecomment-3438700008

nickgrayECA · 8 months ago

In Claude's own words
Bug Title: "Edit tool 'File has been modified' error persists across multiple Read operations in long conversations"

Description:
In long conversation sessions, the Edit tool throws "File has been modified since read" errors even when:

  1. No external process has modified the file
  2. The file was just read moments before the Edit attempt
  3. The file's actual modification timestamp hasn't changed

Expected behavior: Edit should compare against the MOST RECENT Read operation
Actual behavior: Edit appears to compare against an earlier Read from the same conversation session

This makes long coding sessions extremely difficult as files read multiple times become impossible to edit.

Workaround: Use Write tool or bash commands, but this is less safe.

ITAnywhere-Developer · 8 months ago

This remains an issue in Claude 2.0.37. I notice the same does not seem to occur on my Windows 10 machine; if only that was my main. To have this bug present since July is crazy. Time to move on to a working AI, it seems!

dylanh724 · 7 months ago

Any official response?

ITAnywhere-Developer · 7 months ago

Bug is being solved by users so I believe they are ignoring this. Or perhaps they see it as more of a environment quirk than a bug. My main Claude.md has the following instructions to prevent this error from occuring:

"

Windows Environment Rules

  • Operating System: Windows 10/11 (NOT Linux, NOT WSL, NOT Unix)
  • Git Bash Shell: Commands run in Git Bash on Windows (Unix-like commands available)
  • File Paths: ALWAYS use Windows absolute paths like

C:\\Repos\\Xhuma\\xhuma-web-server\\src\\...

  • Maven Commands: Run directly as mvn test, mvn clean install, etc.
  • NO Unix conversions: NEVER use /mnt/c/ path prefixes

"

I believe it's the use of absolute paths that is preventing the bug from reoccuring.

Hope this helps

github-actions[bot] · 6 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

oskar-gm · 6 months ago

Same root cause confirmed on Windows.

Simplest workaround: use relative paths - they're "permissive" and work regardless of how you previously read the file. No need to worry about path separators.

Also found that this bug is exclusive to Edit tool - Write has no path restrictions.

Full analysis and CLAUDE.md workaround: #10437

Raundahl · 6 months ago

This problem is also present on Windows, and is still occurring as of January 2026

oskar-gm · 6 months ago

@Raundahl I found a workaround that works on Windows - see my comment above. Using relative paths for both Read and Edit resolves the issue.

meagerfindings · 2 months ago

This bug is actively blocking production agentic workflows. I run ADW (Agentic Developer Workflow) — a multi-agent pipeline that uses Claude Code as its execution engine to implement, test, review, and ship code through Graphite stacked PRs.

The specific trigger: any process that reads modified files — bundle exec rspec, Ruby require, even rails runner — causes the Edit/Write cache to race against the external file read and silently revert all changes. The agent edits a model, edits a controller, then runs bundle exec rspec to validate — and rspec's require touches every modified source file, causing Claude Code to treat them as "modified since read" and revert to stale content. The implementation vanishes mid-session.

The workarounds (writing files via cat <<'HEREDOC' in Bash, committing before every test run) are fragile and add significant friction. They also defeat the purpose of the Edit tool's safety guarantees.

This has been open since July 2025 — nearly 10 months. The bug family spans at least a dozen issues (#42383, #33856, #25623, #28383, #21201, etc). It is actively driving us to evaluate OpenCode and other coding agents as alternatives, because unattended multi-agent runs cannot function when source files silently revert.

info618 · 1 month ago

<!--
Draft comment to add to https://github.com/anthropics/claude-code/issues/3513
Post with:
gh issue comment 3513 --repo anthropics/claude-code --body-file comment-3513-mtime.md
-->

Confirming on macOS + concrete trigger: PostToolUse formatter hooks

Confirming this bug on macOS 26 / Claude Code (2026-05 vintage). The error is reproducible and the trigger mechanism is identifiable.

What I observed

Scan of ~3 weeks of JSONL transcripts (2026-05-13 → 2026-05-25, single user): 22 sessions hit <tool_use_error>File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.</tool_use_error> mid-turn, blocking subsequent Edit calls until the file was re-Read.

Top targets by frequency:

| File class | Sessions hit |
|---|---|
| MEMORY.md (auto-memory) | 6+ |
| Project CLAUDE.md | 3 |
| .claude/settings.json (user + project) | 2 |
| Plan / log Markdown in guides/*/ | 5+ |
| Worktree files | 2 |
| VS Code config | 1 |

Root cause: PostToolUse hooks rewrite the just-edited file

The error message names the cause itself: "either by the user or by a linter". In this case, the "linter" is the user's own PostToolUse hooks.

The canonical hook chain (registered in ~/.claude/settings.json on matcher "Edit|Write"):

  1. ~/.claude/hooks/post-tool-use/after-edit.sh — runs prettier --write on .md .yaml .yml .css .scss .html, black/ruff on .py, gofmt/rustfmt/shfmt on others.
  2. ~/.claude/hooks/post-tool-use/markdown-formatter.py — auto-tags bare ``` fences and collapses blank lines.
  3. ~/.claude/hooks/post-tool-use/auto-format.sh — runs dart format / prettier --write on .dart .ts .tsx .js .jsx .json.

Sequence

  1. Model calls Edit(file) → harness writes content, mtime = t₁, harness records snapshot.
  2. PostToolUse hooks fire → at least one rewrites the file → mtime = t₂ > t₁.
  3. Model calls Edit(file) again → harness compares snapshot t₁ vs disk t₂ → error.

The error fires both when the formatter actually changes bytes (legitimate, since the model's view of the file is now stale) and when the formatter is a no-op but --write touches mtime anyway (false positive — the model's view is still accurate).

Concrete repro

# minimal hook
cat > ~/.claude/hooks/post-tool-use/touch-mtime.sh <<'EOF'
#!/usr/bin/env bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[[ -f "$FILE" ]] && touch "$FILE"
exit 0
EOF
chmod +x ~/.claude/hooks/post-tool-use/touch-mtime.sh

# register on PostToolUse matcher "Edit|Write" in ~/.claude/settings.json

Then in a session:

  1. Write("/tmp/foo.md", "hello") → succeeds
  2. Edit("/tmp/foo.md", "hello" → "world") → succeeds, hook touches file
  3. Edit("/tmp/foo.md", "world" → "again")"modified since read"

Trace from a real session (MEMORY.md edits, gaps elided):

21:34:38  Edit MEMORY.md → OK
21:39:42  Edit MEMORY.md → OK
22:07:48  Edit MEMORY.md → ERROR ("modified since read")
22:07:51  Read MEMORY.md          ← forced recovery
22:07:59  Edit MEMORY.md → OK
23:04:12  Edit MEMORY.md → ERROR  ← happens again
23:04:16  Read MEMORY.md
23:04:25  Edit MEMORY.md → OK

Each ERROR was preceded by a PostToolUse markdown-formatter.py rewrite of MEMORY.md (auto-tagging code fences).

Proposed mechanism (option A — harness-side, cleanest)

Refresh the snapshot mtime when PostToolUse hooks complete on the same file. The harness already knows: (a) which file the tool wrote, (b) when the PostToolUse phase ends. After the last PostToolUse hook returns, re-stat the file and update the in-memory snapshot. This treats the PostToolUse phase as part of the tool's logical execution, not a foreign modification.

This is correct semantically: PostToolUse hooks are user-configured tail logic of an Edit/Write call. Their modifications are not "external" by any meaningful definition.

Caveat: if the formatter actually changes content (not just mtime), the model's internal view of the file is now stale. Two options:

  • A1. Refresh both mtime and content snapshot. Subsequent Edit with old old_string will fail with a content mismatch (correct, clear error).
  • A2. Refresh mtime only. Trust the model to re-Read when it observes its content changed. Less safe but matches current intent.

A1 is the right call.

Proposed mechanism (option B — user-side workaround, what I shipped locally)

Wrap each formatter call in a mtime-preserve helper: hash file before + after, restore mtime if hashes match. Handles the common case (formatter is no-op on already-formatted code) but not the case where formatter actually changes bytes. Sample:

run_format() {
    local file="$1"; shift
    local mtime_before hash_before hash_after
    mtime_before=$(date -r "$file" "+%Y%m%d%H%M.%S" 2>/dev/null) || mtime_before=""
    hash_before=$(shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1) || hash_before=""
    "$@" 2>/dev/null || true
    hash_after=$(shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1) || hash_after=""
    if [[ -n "$mtime_before" && "$hash_before" == "$hash_after" ]]; then
        touch -t "$mtime_before" "$file" 2>/dev/null || true
    fi
}

Drops false-positive errors by ~80% in my measurements. Does not fix the legitimate-change case — that one needs harness option A1.

What Anthropic has already shipped (partial fix, observed live)

While preparing this comment, I observed that a partial fix is already present in current Claude Code. After a Write whose target is then touched by a PostToolUse hook, the harness emits a system reminder to the model:

<system-reminder>
PostToolUse:Write hook additional context: PostToolUse hook modified <path>
after your edit (likely a formatter). Your next Edit will not fail with a
stale-file error, but if its old_string targets a region the hook reformatted,
Read the file first.
</system-reminder>

This is option A2 from above — the harness refreshes its internal mtime tracker so the next Edit does not fail with a stale-file error, and it warns the model that content may have drifted.

This fixes the false-positive case (formatter is a no-op on already-formatted code → next Edit succeeds). It does not fix the legitimate-change case: the model's view of the file content is still pre-format, so any subsequent Edit whose old_string lies in a reformatted region must defensively Read first, burning tokens.

What's still needed for the full fix

  1. Content-snapshot refresh, not just mtime. When the PostToolUse hook changes bytes, the harness should either (a) refresh the model's internal content view to the post-format bytes, or (b) inline the post-format content (or a diff) in the system reminder so the model doesn't need a defensive Read. Option (a) is cleaner; (b) is more transparent.
  2. Coverage for cross-turn modifications. The current reminder fires after a tool call in the same turn. Files modified between turns (e.g., the auto-memory subsystem writing to MEMORY.md) still produce the original error class with the original misleading message ("either by the user or by a linter"). Filed separately: #62336.
  3. Provenance attribution in the residual error. When the error does still fire (cross-turn writes, hooks running outside the harness's tracking, etc.), the message should attribute the modification ("modified by your PostToolUse hook" / "modified by the auto-memory subsystem" / "modified externally") instead of blaming the user.

Why this matters

For agentic workflows that orchestrate multiple PRs through formatter-equipped repos (Flutter / TypeScript / Markdown), the residual error fires multiple times per session and burns tokens on Read-retry loops. Per the 2026-05-02 comment by @meagerfindings, it's a production blocker for stacked-PR workflows.

The partial fix (mtime refresh + reminder) eliminates the spurious-failure case. The remaining content-drift case still costs a defensive Read per affected Edit — solvable with the content-snapshot refresh in (1) above.