Claude memory tool: path → customId normalization collides, silently overwriting unrelated memory files

Author: rajarshidattapyCreated Aug 19, 2026Updated Sep 17, 2026
Labelsbug

Severity: Medium (silent data loss) File: packages/tools/src/claude-memory.ts:52-58

Description

normalizePathToCustomId flattens both / and . to _:

typescript
private normalizePathToCustomId(path: string): string {
    return path
        .replace(/^\//, "")   // strip leading slash
        .replace(/\//g, "_")  // / -> _
        .replace(/\./g, "_")  // . -> _
}

The mapping is not injective. All of these produce the identical customId memories_notes_txt:

Path customId
/memories/notes.txt memories_notes_txt
/memories/notes_txt memories_notes_txt
/memories/notes/txt memories_notes_txt

And /memories/project/a.md collides with /memories/project_a.md.

Since customId is the document identity for client.add(), create, str_replace, insert, rename, and delete, a collision means:

  • create on the second path overwrites the first file's content with no warning — the tool reports success.
  • view on either path returns whichever document won last.
  • delete on either path destroys both.

Nested memory directories are the realistic trigger: Claude's memory tool routinely creates paths like /memories/projects/foo.md, and any sibling /memories/projects_foo.md (or a second nesting level that flattens the same way) silently clobbers it.

Suggested fix

Make the encoding reversible — escape the separator before substituting it, e.g. path.replace(/_/g, "__").replace(/[/.]/g, "_"), or drop the transformation entirely and hash the path (customId = "mem_" + sha256(path).slice(0, 32)) if the backend accepts arbitrary customIds. Either way, add a round-trip assertion for the three colliding paths above.

Source: supermemoryai/supermemory