Skip to content

fix(MemoryGraph): first-wins on duplicate KNOWLEDGE slugs (drop UsageGraphError crash)#1408

Open
Luo0oo wants to merge 1 commit into
danielmiessler:mainfrom
Luo0oo:fix/memorygraph-slug-collision
Open

fix(MemoryGraph): first-wins on duplicate KNOWLEDGE slugs (drop UsageGraphError crash)#1408
Luo0oo wants to merge 1 commit into
danielmiessler:mainfrom
Luo0oo:fix/memorygraph-slug-collision

Conversation

@Luo0oo

@Luo0oo Luo0oo commented Jul 3, 2026

Copy link
Copy Markdown

Bug

buildGraph at LifeOS/install/LifeOS/TOOLS/MemoryGraph.ts:288 iterates KNOWLEDGE domains (People/Companies/Ideas/Research) and derives node IDs from bare filenames:

const slug = entry.replace(/\.md$/, "");
raws.push({ node: { id: slug, silo: "knowledge", ... } });

No domain prefix, so any two files sharing a basename across domains produce identical IDs. The second graph.addNode(node.id, ...) throws:

UsageGraphError: Graph.addNode: the "synthesis" node already exist in the graph.
    at addNode (…/graphology.mjs:3708:11)
    at buildGraph (…/MemoryGraph.ts:288:11)

The crash blocks the whole build. graph.json never regenerates, PATTERNS.md stays stale, Pulse's /api/memory/graph route silently serves nothing.

Fix

Guards graph.addNode with hasNode, matching the existing first-wins semantics of the bareToId population two lines below (if (!bareToId.has(bare)) bareToId.set(...)).

Emits console.warn listing the kept-path and skipped-path so users notice the data loss. MemoryGraph.ts is a report-generator tool with multi-line human output, not a scriptable pipe; a silent drop would hide files the user wrote.

Reproducer

mkdir -p ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/{Companies,Research}
printf '%s\n' '---' 'title: A' '---' 'body' > ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/Companies/foo.md
printf '%s\n' '---' 'title: B' '---' 'body' > ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/Research/foo.md
bun ~/.claude/LIFEOS/TOOLS/MemoryGraph.ts build
# crashes without this fix
# With this fix: builds cleanly, prints:
# [MemoryGraph] slug collision: kept .../Companies/foo.md, skipped .../Research/foo.md

Orthogonal issues noticed (out of scope — worth separate PRs)

  • KNOWLEDGE-first ordering in ingest() means WORK ISAs lose bare-slug wikilink resolution when a KNOWLEDGE file shares the basename
  • patterns command rebuilds only on 60-min cache age, never checks source file mtimes — edit a note, get stale patterns silently
  • addEdge widens weight on collision but never upgrades kind (a later related edge over an earlier tag keeps kind tag — provenance loss)

Verify

Reproducer above passes with this diff applied. Existing populated KNOWLEDGE trees without collisions are unchanged (the guard is a no-op path).

…GraphError crash)

buildGraph iterates KNOWLEDGE domains (People/Companies/Ideas/Research)
and derives each node's id from the bare filename via entry.replace(/\.md$/, '').
No domain prefix, so any two files sharing a basename across domains produce
identical ids and the second addNode throws:

  UsageGraphError: Graph.addNode: the "synthesis" node already exist in the graph.

The crash blocks the whole build; the graph never renders and PATTERNS.md
never regenerates, silently breaking Pulse's /api/memory/graph route.

Guards graph.addNode with hasNode, matching the existing first-wins
semantics of the bareToId population two lines below. Emits console.warn
listing kept-path and skipped-path so users notice the data loss (this is
a report-generator tool, not a scriptable pipe — silent drop would hide
files they wrote).

Reproducer:
  mkdir -p ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/{Companies,Research}
  printf '%s\n' '---' 'title: A' '---' 'body' > ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/Companies/foo.md
  printf '%s\n' '---' 'title: B' '---' 'body' > ~/.claude/LIFEOS/MEMORY/KNOWLEDGE/Research/foo.md
  bun ~/.claude/LIFEOS/TOOLS/MemoryGraph.ts build   # crashes without this fix

Orthogonal issues noticed but out of scope (worth separate PRs):
- KNOWLEDGE-first ordering in ingest() means WORK ISAs lose bare-slug
  wikilink resolution when a KNOWLEDGE file shares the basename
- 'patterns' command rebuilds only on 60min cache age, never checks source
  mtimes — user can edit a note and get stale patterns silently
- addEdge widens weight on collision but never upgrades kind (a later
  'related' edge over an earlier 'tag' keeps kind 'tag' — provenance loss)
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.

1 participant