Project paths containing `_` end up in two `~/.claude/projects/` keys; memory and session state silently split between them on Windows

Resolved 💬 6 comments Opened Apr 27, 2026 by jamesoliverh Closed Jun 2, 2026

On Windows, a project path containing an underscore produces two parallel storage directories under ~/.claude/projects/:

  • C--dev-physics-project/ — every separator (\, /, :, _) replaced with -
  • C--dev-physics_project/ — separators replaced with -, but _ preserved

Different Claude Code subsystems write to different variants. On my machine this caused silent loss of memory (the memory/*.md files Claude Code persists per project): writes accumulated in one variant for one period and the other for another, with no warning. New sessions read from whichever key the current binary picks and behave as if there is no prior memory, even though the files still exist at the parallel key.

Reproducer

  1. On Windows: mkdir C:\dev\foo_bar (any path with _).
  2. cd C:\dev\foo_bar and start Claude Code. Have a session that writes to memory.
  3. Check ~/.claude/projects/. Both C--dev-foo-bar/ and C--dev-foo_bar/ may exist, with state split between them.

Concrete evidence on my machine

Two affected projects, both with _ in the path.

C:\dev\physics_project:

| Variant | Memory files | Session JSONLs | Newest mtime |
|---|---|---|---|
| C--dev-physics-project/ | 0 | 1 (today) | 2026-04-27 |
| C--dev-physics_project/ | 33 | 2 (older) | 2026-04-27 |

Session-storage keying flipped from underscore to hyphen on 2026-04-27. Memory written by older underscore-keyed sessions is invisible to today's hyphen-keyed session.

C:\dev\ml_project:

| Variant | Memory files | Session JSONLs | Newest mtime |
|---|---|---|---|
| C--dev-ml-project/ | 7 (newest 2026-04-22) | 9 (since 2026-04-14) | 2026-04-22 |
| C--dev-ml_project/ | 13 (newest 2026-04-16) | 0 | 2026-04-16 |

Sessions always went to hyphen. Memory writes shifted from underscore to hyphen around 2026-04-16. The two file sets are disjoint by name (no overlap), so half of the accumulated memory is unreachable from any given session.

Version / timing

  • claude --version: 2.1.119
  • Local claude.exe mtime: 2026-04-24 00:36:44 (matches the public 2.1.119 release)
  • autoUpdatesChannel: "latest" in global settings.json
  • ml_project memory cutover: ~2026-04-16
  • physics_project session-storage cutover: 2026-04-27

The two cutovers are on different dates and in different subsystems, so at least two distinct path-key code paths exist with inconsistent rules. Neither cutover lines up exactly with the local binary install date, so the trigger is not purely the binary version on this machine.

Related issues

  • #19910 (closed, not planned) — duplicate .claude.json entries from Windows path format inconsistency. Same root-cause class; different surface (config file vs ~/.claude/projects/).
  • #19972 — encoding rule replaces non-alphanumerics with -.
  • #27706 — Windows path normalisation duplicates.
  • #53340 — project-level folders missing in v2.1.119 sidebar; possibly the same keying flip surfacing in the UI.

The novel aspect here is silent data loss, not just config pollution: the memory/*.md files are how Claude Code persists what it has learned across sessions. When sessions read from a different key than where memory was written, the assistant behaves as if it has no prior context, and there is no user-visible signal that the files still exist at the parallel key.

Proposed fix

  1. One canonical path-key sanitisation function, used by all subsystems (sessions, memory, settings, config). Document the rule.
  2. One-time migration on first launch after the fix: detect duplicate keys for the same project (pairs differing only in _/-), merge memory and session JSONLs, report the merge to the user.
  3. If the sanitisation rule changes in a future release, refuse to silently re-key: warn and offer migration.

Local workaround

For Windows users hitting this now:

  1. Identify the canonical variant — usually whichever has the most recent / largest content (often the underscore-preserving one).
  2. Copy any missing files from the other variant into canonical. Merge MEMORY.md indexes by hand.
  3. Delete the non-canonical memory/ directory.
  4. Create a directory junction so the non-canonical path resolves to canonical:

``powershell
New-Item -ItemType Junction

-Path 'C:\Users\james\.claude\projects\C--dev-foo-bar\memory'
-Target 'C:\Users\james\.claude\projects\C--dev-foo_bar\memory'
``
No admin needed (junction, not symlink). Confirm both paths resolve to the same files afterwards.

Session JSONLs can stay where they are — no race risk per file.

Environment

  • OS: Windows 11 Pro 10.0.26200
  • Shell: Git Bash and PowerShell (both observed; not the shell-format issue from #19910)
  • Claude Code: 2.1.119
  • Auto-update: enabled

Severity

High for any user with _ in a project path. Silent data loss with no user-visible warning is the failure mode.

View original on GitHub ↗

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