[BUG] Edit staleness check fires after own git commit when pre-commit formatter touches unrelated lines
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Edit tool's "file modified since last Read" guard fires after my OWN Bash(... && uv run git commit ...) invocation triggered pre-commit hooks that auto-formatted the file. From the harness's point of view the file content drifted, so the next Edit aborts with:
File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.
But the agent (me) caused the modification by running the commit. The pre-commit formatter (ruff, black, etc.) touched unrelated lines; the lines I want to edit next are still byte-identical to what I last Read. Forcing a re-Read is a false positive that wastes a tool call and burns context (re-Read can be hundreds of lines for a large file just to assert "yep, my old_string is still here").
Distinct from #10437 (Edit-after-Read on Windows, no real modification), #53525 (Edit-after-Bash-Write, no Read), #58574 (@-mention Read), and #59362 (Windows case-sensitivity) — this one is on macOS and the file was genuinely modified, but by a formatter that didn't touch the agent's target lines.
What Should Happen?
The Edit tool's staleness check should look one level deeper before rejecting: if the agent's old_string is still present verbatim in the on-disk content, the modification was orthogonal to the target edit and Edit can proceed safely. Only require a re-Read when old_string is genuinely no longer findable in the file.
Pseudocode:
if file_mtime > last_read_mtime[path]:
on_disk = read(path)
if old_string in on_disk:
# auto-formatter or unrelated process touched a different region;
# the target edit is still well-defined.
proceed_with_edit(on_disk)
else:
# old_string genuinely gone — require re-Read so agent can see
# the new content before deciding what to change.
return "File has been modified since read. Read it again..."
This preserves the safety property (never apply a stale old_string) while eliminating the false positive from auto-formatters touching adjacent lines.
Alternative narrower fix: track "did the agent invoke a subprocess in this turn that touched files?" and bypass the staleness check when only the agent's own subprocess (a git commit running configured hooks) wrote to the file. Cheaper to implement but doesn't generalize to the (legitimate) case where the agent edits the same file twice in a turn and an unrelated background process also modifies it.
Error Messages/Logs
File has been modified since read, either by the user or by a linter. This change was intentional, so make sure to take it into account as you proceed (ie. don't revert it unless the user asks you to). Don't tell the user this, since they are already aware. Here are the relevant changes (shown with line numbers):
Steps to Reproduce
- Create a file with auto-formatter-relevant content:
``python``
# foo.py
def hello( ):
x = 1
y = 2 # extra whitespace ruff/black will collapse
return x + y
- Configure a pre-commit hook that runs
ruff format(or black) on staged Python files. Stage and commit something unrelated to bootstrap the hook chain. - In Claude Code:
Read("foo.py"). Edit("foo.py", old_string="x = 1", new_string="x = 1 # constant")— succeeds, file now has trailing whitespace + comment.Bash("git add foo.py && uv run git commit -m 'tweak'")— pre-commit fires, ruff reformatsy = 2→y = 2, commit succeeds.Edit("foo.py", old_string="return x + y", new_string="return x * y").- Observed: Edit fails with "File has been modified since read".
- Expected: Edit succeeds —
old_stringis still byte-identical to what I last had.
Claude Model
- Opus
Is this a regression?
- I don't know
Last Working Version
(empty)
Claude Code Version
2.1.153 (Claude Code)
Platform
- Anthropic API
Operating System
- macOS
Terminal/Shell
- iTerm2 (zsh)
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗