[BUG] Read/Edit tracker is case-sensitive on Windows; drive-letter casing mismatch triggers "File has not been read yet"
Bug Description
On Windows, the harness's internal "this file has been read" tracker uses a case-sensitive string comparison on the file path. Because Windows filesystems are case-insensitive, two paths that resolve to the same physical file (e.g. c:\Users\…\foo.md and C:\Users\…\foo.md) are treated as different entries. A successful Read registered under one casing does not satisfy a subsequent Edit/Write under different casing, and the tool errors with:
File has not been read yet. Read it first before writing to it.
This forces a redundant Read of a file that was already fully read in the same conversation.
Steps to Reproduce
- In a user message, type a file path with a lowercase drive letter, e.g.
c:\Users\me\project\notes.md. - Have Claude
Readthat exact path. It succeeds. - Have Claude
Globor otherwise resolve a related path in the same project — on Windows,Globreturns paths with an uppercase drive letter (C:\Users\me\project\…). - Have Claude
Editthe samenotes.mdusing the uppercase path returned by tooling. - The Edit fails with
File has not been read yet. Read it first before writing to it. - Re-reading the file with the uppercase path makes the next Edit succeed.
Expected Behavior
c:\…\notes.md and C:\…\notes.md resolve to the same file on Windows. A Read of one should satisfy the read-before-edit guard for the other. The harness should normalize tracker keys so casing variance is absorbed.
Actual Behavior
The tracker stores the path verbatim as the lookup key. Casing mismatch between Read and Edit produces a false-negative "not read yet" error, even within the same turn.
Environment
- Platform: win32
- OS: Windows 11 Home 10.0.26200
- Shell: PowerShell 7
- Model: claude-opus-4-7 (Opus 4.7)
- Claude Code version: 2.1.142
Suggested Fix
Normalize tracker keys on insert and lookup, win32 only:
path.normalize(p)to collapse separators.- Lowercase the drive letter (
p[0].toLowerCase() + p.slice(1)), or lowercase the entire path on win32 (filesystem is case-insensitive anyway). - Optionally
fs.realpathSync.native(p)to canonicalize through symlinks / junctions / 8.3 short names.
Apply the same normalization in every place a path is used as a Map/Set key for tool state: read-tracker, file-state, "unexpectedly modified" check, etc.
Related Issues
- #7536 — Windows case-sensitive path matching for
/doctorPATH detection. Same root cause class, different surface. - #21291 — File read state lost after user-message interruption. Different trigger, overlapping symptom.
- #46586 — MCP config conflict from project-path case sensitivity on Windows.
- #8625 — Global CLAUDE.md loaded twice due to path case mismatch on Windows.
These all point to the same underlying issue: paths are used as keys without win32-aware normalization. A single shared normalizePathKey(p) helper applied at every cache/tracker boundary would close this class of bug.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗