Auto-memory creates duplicate project directories when path contains underscores

Resolved 💬 3 comments Opened Mar 26, 2026 by Drozes Closed Mar 30, 2026

Description

The auto-memory system creates duplicate project directories under ~/.claude/projects/ when the working directory path contains underscores. This causes memory fragmentation — some memories are saved to one directory, some to the other — with no mechanism to detect or reconcile the split.

Root Cause

The project slug function (found in both cli.js and extension.js) correctly normalizes paths:

function projectSlug(path) {
  let slug = path.replace(/[^a-zA-Z0-9]/g, "-");
  if (slug.length <= 200) return slug;
  let hash = hashFunction(path).toString(36);
  return `${slug.slice(0, 200)}-${hash}`;
}

This replaces all non-alphanumeric characters — including underscores — with hyphens. Session .jsonl files are always stored under the correctly slugged directory.

However, the LLM's Write tool calls for auto-memory sometimes compute their own path from the raw cwd (preserving underscores) instead of using the slug-derived path provided in the system prompt. This creates a second project directory containing only a memory/ subdirectory.

Evidence

Working directory: /Users/<user>/code/new_dir/code-review-agents

Two project directories exist:

| Directory slug | Created | Contents |
|---|---|---|
| -Users-...-code-new-dir-code-review-agents (correct, hyphenated) | Mar 9 | 102 items: sessions + memory |
| -Users-...-code-new_dir-code-review-agents (underscore preserved) | Mar 19 | Only memory/ (no sessions) |

This pattern repeated for all 4 projects under the new_dir/ parent:

| Project | Underscore dir | Hyphen dir |
|---|---|---|
| code-review-agents | memory/ only | 102 items (sessions + memory) |
| PM-Local | memory/ only | 68 items |
| expo-apps | memory/ only | 18 items |
| microservice-project | memory/ only | 50 items |

The underscore directories contain only memory/ — no session files — confirming they were created by the LLM's Write tool, not by Claude Code's internal file I/O.

Steps to Reproduce

  1. Create a working directory with underscores in the path (e.g., ~/code/my_project/my-repo)
  2. Open Claude Code in that directory
  3. Interact across multiple sessions so auto-memory writes files
  4. Inspect ~/.claude/projects/ — two directories may exist for the same project

Expected Behavior

A single project directory using the canonical slug, with all sessions and memories co-located.

Actual Behavior

Two project directories:

  • Hyphenated slug (correct): contains sessions and some memories
  • Underscore-preserving slug (incorrect): contains only memory/

Memories are fragmented across both. Which directory gets loaded depends on the session, causing inconsistent context. The additionalDirectories setting in .claude/settings.json can further entrench the split by hardcoding one path.

Batch operations like the "dream" consolidation command compound the problem — they write output to one directory while the active session reads from the other, silently losing work.

Impact

  • Split-brain memory: sessions see incomplete history, leading to repeated questions and lost preferences
  • Silent data loss: consolidation/cleanup operations affect only one directory
  • User confusion: no warning when duplicates are created, no tooling to detect or merge them
  • Scales with underscores: every project under a parent directory with underscores gets its own duplicate

Suggested Fix

Option A (system prompt): Ensure the memory path surfaced to the LLM always uses the slug function output, so Write tool calls target the canonical directory.

Option B (server-side validation, more robust): When a Write tool call targets ~/.claude/projects/, canonicalize the path segment through the slug function before executing the write:

function canonicalizeProjectPath(writePath) {
  const match = writePath.match(/\.claude\/projects\/([^/]+)/);
  if (match) {
    const canonical = projectSlug(/* resolve slug back to original path or re-slug */);
    return writePath.replace(match[1], canonical);
  }
  return writePath;
}

Option B is more robust since it does not depend on the LLM faithfully copying a path from the system prompt.

Environment

  • OS: macOS (Darwin 24.6.0)
  • Shell: zsh
  • Interface: VS Code extension + CLI
  • Claude Code: Latest as of 2026-03-26

View original on GitHub ↗

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