[BUG] Read/Edit tracker is case-sensitive on Windows; drive-letter casing mismatch triggers "File has not been read yet"

Resolved 💬 6 comments Opened May 15, 2026 by KamilDev Closed Jun 23, 2026

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

  1. In a user message, type a file path with a lowercase drive letter, e.g. c:\Users\me\project\notes.md.
  2. Have Claude Read that exact path. It succeeds.
  3. Have Claude Glob or otherwise resolve a related path in the same project — on Windows, Glob returns paths with an uppercase drive letter (C:\Users\me\project\…).
  4. Have Claude Edit the same notes.md using the uppercase path returned by tooling.
  5. The Edit fails with File has not been read yet. Read it first before writing to it.
  6. 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:

  1. path.normalize(p) to collapse separators.
  2. Lowercase the drive letter (p[0].toLowerCase() + p.slice(1)), or lowercase the entire path on win32 (filesystem is case-insensitive anyway).
  3. 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 /doctor PATH 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.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗