[BUG] Edit reports success and reads back the edit, but never writes to disk (worktree + PreToolUse hook; no sandbox)

Status Open
Reported on v2.1.89
Maintainer reply None cached
Activity 0 comments · opened Aug 11, 2026

Summary

In a git worktree with a PreToolUse hook registered on Edit|Write|MultiEdit, the Edit tool reported success and read back the edited content, but the change never reached the filesystem. Every Bash-side observer (grep, git diff, node, python3, stat) saw the original file, including with dangerouslyDisableSandbox: true.

This is closely related to a cluster of issues closed as NOT_PLANNED — #40227, #35699, #52463 — but the trigger here is different: no sandbox is configured (#35699's trigger), the path is absolute and correct (#52463 / #60679's trigger), and writes to other files in the same session and same directory persisted fine.

The failure is silent and, without an independent verification step, produces confidently wrong work: an edit is "applied", the model proceeds, and the resulting commit is empty or partial.

Reproduced on Claude Code 2.1.89 (macOS).

Relevant configuration

Not machine specifics — these are the conditions that appear to gate the bug:

  • Running in a git worktree (<repo>/.claude/worktrees/<name>), not the primary checkout
  • No sandbox key in project, local, or user settings
  • A PreToolUse hook registered on Edit|Write|MultiEdit — a Node script that exits 0 to allow and 2 to block, with an early-exit branch for certain exempt paths

Reproduction / isolation matrix

Same session, same worktree, same directory. Each row is an Edit or Write, followed immediately by a Bash grep for the inserted marker:

| Target | Pre-existing? | Git-tracked? | Allowed by hook via | Persisted? |
|---|---|---|---|---|
| notes/<file>.md | yes | yes | early-exit (exempt path) | ✅ yes |
| notes/<new>.txt | no | no | early-exit (exempt path) | ✅ yes |
| lib/<dir>/<new>.js | no | no | full evaluation path | ✅ yes |
| lib/<dir>/a.js | yes | yes | full evaluation path | ❌ no |
| lib/<dir>/b.js | yes | yes | full evaluation path | ❌ no |
| lib/<dir>/_guard.js | yes | yes | full evaluation path | ❌ no |

The last three failed repeatedly and deterministically across ~6 attempts spanning ~40 minutes. The first three succeeded on first try.

The distinguishing factor appears to be pre-existing file + reaching the hook's full evaluation path (vs. its early-exit branch). Creating a new file on that same full path persisted, so it is not simply "hook ran → write dropped".

Observed behaviour

  1. Edit returns The file ... has been updated successfully.
  2. A subsequent Read of the same path shows the edited content (so the edit exists somewhere in-process).
  3. grep / git diff / node -c / python3 via Bash all show the original content.
  4. stat reports the pre-edit mtime and byte size.
  5. A system-reminder later arrived stating "file was modified, either by the user or by a linter" and quoted the pre-edit content — the same signature reported in #35699.
  6. Writing the identical change with python3 via Bash persisted immediately and was committable.

Ruled out during isolation: extended attributes and file flags (identical to files that wrote fine), .gitattributes filters, open file handles (lsof clean), symlink/path aliasing (realpath correct), a stray second settings file, and read-side sandboxing (dangerouslyDisableSandbox: true on the verifying command changed nothing).

Impact

Silent and high-consequence. In this session it produced a commit that would have been empty had the edit not been independently verified — the model had already been told the edit succeeded and had re-read it as applied. Any workflow that edits and then commits without a git diff check can ship nothing while reporting success.

Workaround

Apply the change through Bash and verify before trusting it:

# write via python3/Bash, with a guard that the match is unique
assert content.count(old) == 1
content = content.replace(old, new)
open(path, 'w').write(content)

then confirm with git diff before committing. Note that Read is not a sufficient check — it returned the phantom edited content throughout.

Suggested fix

Whatever the underlying cause, the Edit/Write tools should fail loudly rather than report success: verify the write landed (re-stat / re-hash after write) and surface an error if it did not. Given three prior issues with this same "reports success, no write" shape closed as NOT_PLANNED, a post-write verification in the tool itself would convert an entire class of silent data loss into a visible error.

View original on GitHub ↗