Skip to content

fix(tools): stop memory file paths from colliding on one customId - #1566

Open
rajarshidattapy wants to merge 1 commit into
supermemoryai:mainfrom
rajarshidattapy:fix/memory-path-customid-collision
Open

fix(tools): stop memory file paths from colliding on one customId#1566
rajarshidattapy wants to merge 1 commit into
supermemoryai:mainfrom
rajarshidattapy:fix/memory-path-customid-collision

Conversation

@rajarshidattapy

Copy link
Copy Markdown
Contributor

Fixes #1547

Problem

normalizePathToCustomId flattened /, . and _ all to _, so the mapping was not injective:

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

customId is the document identity for create, view, str_replace, insert, rename and delete, so two paths sharing one id meant create silently overwrote an unrelated file, view returned whichever document won last, and delete destroyed both. Nested memory directories (/memories/projects/foo.md vs /memories/projects_foo.md) are the realistic trigger.

Fix

Escape the separators instead of flattening them, making the encoding reversible:

__ = literal _    _s = /    _d = .

Every _ in the output opens a two-character sequence, so decoding is unambiguous and distinct paths always get distinct ids. /memories/notes.txt now maps to memories_snotes_dtxt.

Hashing was the other option in the issue, but getFileDocument passes the customId as the semantic search query (q) and then exact-matches the result — a hex digest as the query would stop retrieving the document at all.

Existing documents

Changing the encoding orphans anything written under the old ids, which would be its own silent data loss. So getFileDocument — the single lookup every command routes through — falls back to the legacy id, guarded by an exact metadata.file_path match, since legacy ids are the ambiguous ones and must not be trusted on their own. The next write to that path re-adds it under the new id, so the fallback self-heals rather than lingering. The search query stays the legacy (word-preserving) form, so search relevance is unchanged.

Tests

Added to packages/tools/src/claude-memory.test.ts:

  • the five colliding paths from the issue now produce five distinct customIds
  • a legacy-id document with a matching file_path is still readable
  • a legacy-id document with a different stored file_path is not served (reports not-found)

Existing tests that hardcoded the old ids were updated to the new encoding. claude-memory.test.ts and tool-operations.test.ts: 27/27 pass. The two failing files elsewhere in packages/tools (the stale test/ directory importing ./claude-memory) fail identically on main and are untouched here.

`normalizePathToCustomId` flattened `/`, `.` and `_` all to `_`, so
/memories/notes.txt, /memories/notes_txt and /memories/notes/txt shared the
customId `memories_notes_txt`. Since customId is the document identity for
create/view/str_replace/insert/rename/delete, creating one path silently
overwrote another, and deleting one destroyed both.

Escape the separators instead (`__` = `_`, `_s` = `/`, `_d` = `.`) so the
encoding is reversible and distinct paths always get distinct ids. Reads fall
back to the old id when the stored file_path matches exactly, so documents
written before this change stay reachable and get promoted on the next write.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant