[BUG] Path encoding scheme changed between versions, silently fragments project memory across two ~/.claude/projects/ dirs

Resolved 💬 4 comments Opened Apr 28, 2026 by bwagner Closed May 30, 2026

Related to but distinct from #19972 (auto-closed-as-inactive without maintainer engagement, 1 thumbs-down on the close comment, 3 confirmed duplicates: #9221, #7009, #14696).

#19972 documents the steady-state encoding collisions where /, \, -, _ all collapse to -. This issue documents a related and arguably worse migration symptom: the encoding rules have changed between Claude Code versions, and the harness now writes the same project's memory to two different encoded directories depending on which version is running. Existing memory ends up fragmented across both spellings with no warning.

Reproduction (real evidence from my system)

Project on disk: /Users/bwagner/projects/get_invoices

The harness has produced both encodings of this single project's memory directory:

  • ~/.claude/projects/-Users-bwagner-projects-get_invoices/ ← underscores preserved
  • ~/.claude/projects/-Users-bwagner-projects-get-invoices/ ← underscores collapsed to -

File mtimes show writes flip-flopping between the two encodings across Claude Code updates:

| Date range | Active dir | Encoding rule |
|---|---|---|
| Mar 25 - Apr 22, 2026 | …-get_invoices/memory/ | only /- (_ preserved) |
| Apr 23 - Apr 28, 2026 | …-get-invoices/ | both / and _- |

What this actually does to user data

Concrete consequences for me, today:

  • BACKLOG.md exists in both dirs with different content (one has the current rename plan; the other has older unfinished handler items)
  • decisions.md exists in both with different content (older has 14 entries, newer has 7 — no overlap)
  • MEMORY.md (the auto-memory index) was effectively rebuilt from scratch when the encoding flipped. Older entries (feedback memories, project context) silently disappeared from the start-of-session context the assistant loads
  • A documented /start-session workflow that reads ~/.claude/projects/<encoded>/memory/MEMORY.md only sees one of the two and gets a partial picture of the project state
  • The assistant happily uses the partial state, makes recommendations based on it, and only discovers the split when the user happens to ask "where is BACKLOG.md?"

This isn't a hypothetical collision — it's an active, ongoing fragmentation bug for any user whose Claude Code install has been updated across the encoding-scheme change.

Why this is worse than #19972's collision case

  • Collisions in #19972 are visible at the moment they happen (two on-disk projects share one encoded dir; sessions interleave; user notices).
  • The migration case here is silent: the user upgrades Claude Code, opens what they think is the same project, and the harness writes to a fresh empty memory dir. The old memory still exists on disk but is never read again. The assistant's startup context is missing months of project memory and nothing flags this.
  • Recovery requires the user to know that two encodings exist (undocumented), then perform a manual three-way merge across BACKLOG / decisions / MEMORY content.

Companion bug: SessionStart hook stderr is swallowed

After hitting this once, I added a SessionStart hook that scans ~/.claude/projects/ for hyphen/underscore twin pairs and prints a warning to stderr. The warning never surfaced into the assistant's context even though the hook ran and wrote to stderr cleanly when invoked manually. I had to move the same script to UserPromptSubmit to actually get visibility. If SessionStart hooks are intended to be silent-by-design, that should be documented; if not, that's a separate bug worth tracking. Either way it means the natural place for a "your install is in a degraded state" warning currently doesn't reach the assistant or the user.

Workaround I'm using (and what it costs)

Manual symlinks between the two encoded dirs so writes from either spelling converge — same approach @athola described on #19972. For the active-session twin I had to do partial symlinks (BACKLOG.md, decisions.md, project_state.md, memory/) since per-session jsonls already had open handles in the wrong-spelling dir. For the other 4 underscore-named projects on my system I preempted with whole-dir symlinks.

Total time cost: ~45 minutes of debugging/cleanup for one project, plus a SessionStart+UserPromptSubmit hook to detect future regressions, plus per-project preemptive symlinks. Repeated each time the encoding rule shifts.

Minimum acceptable fix

Pick any stable encoding and never change it again. Additionally, persist the original cwd inside the encoded dir as a metadata file (suggested by @athola on #19972) so:

  1. Future encoding-rule changes can read the metadata and migrate cleanly.
  2. Third-party tools and /start-session-style flows resolve unambiguously.
  3. Twin-detection logic can compare original-cwd metadata instead of guessing from the encoded name.

Why #19972 should not have been auto-closed

The auto-close comment received a 👎 immediately. Three confirmed duplicates exist on that thread. No maintainer ever responded. The bot's "inactive for too long" rule is firing on issues that have only failed to receive engagement — which is the opposite signal from what auto-close should respond to.

Environment

  • Claude Code: current (Apr 2026 build, Opus 4.7 1M-context binding active; affects all model bindings since the bug is in the harness path-encoding layer)
  • macOS Darwin 25.3.0
  • Filesystem: APFS

Happy to provide further reproduction artifacts (full file-mtime listing, symlink-fix script, twin-detection hook source) if useful.

View original on GitHub ↗

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