Summary
When two files mention the same entity, graphify keeps the node from whichever file was extracted first and drops the other:
[graphify] WARNING: node 'X' is minted by two different files — keeping 'X' from 'A.md',
dropping 'X' from 'B.md'. ... the dropped node is lost.
The practical effect is that the page that actually defines a concept can lose its own node, and the surviving node is attributed to a file that merely mentions the concept in passing.
Concrete case
Corpus: an Obsidian vault of ~60 markdown notes, graphify extract wiki/ --mode deep.
|
|
| Page that defines the concept |
concepts/작업 단위 폴더 + README 진입점 컨벤션.md (filename == concept name) |
| Page that only mentions it |
concepts/LLM Schema - CLAUDE.md를 가이드로 활용하기.md |
Node's source_file in graph.json |
the mentioning page |
| Nodes attributed to the defining page |
none |
The concept itself is in the graph with 4 edges — only the ownership is wrong.
Why it matters
The documented navigation pattern is "read GRAPH_REPORT.md → identify relevant nodes → fetch only those source files". When source_file points at a page that merely mentions the concept, that workflow routes the reader to the wrong file. The authoritative page is invisible to the graph.
The documented workaround does not apply here
The warning suggests:
To keep them distinct, run 'graphify extract' per subfolder and merge with 'graphify merge-graphs'.
Both files above live in the same folder (concepts/), so splitting by subfolder cannot separate them.
Post-hoc correction is also blocked
Rewriting source_file in graph.json to point at the defining page does not work, because node ids are re-derived from it — build.py::_semantic_id_remap:
the new id is computed from source_file in code, never trusted from the fragment's own id string
So rewriting source_file causes ids to be re-derived, collide with other nodes, and get silently merged — reproducing the very problem. The resulting graph shrank 146 → 145 nodes, which then tripped the shrink guard in export.py:
[graphify] WARNING: new graph has 145 nodes but existing graph.json has 146 (net -1).
Refusing to overwrite.
graph.json NOT written: ... GRAPH_REPORT.md ... left untouched.
Both graphify label and graphify cluster-only refuse to write, so GRAPH_REPORT.md can no longer be regenerated and goes stale. GRAPHIFY_FORCE=1 is honored by extract but not by label / cluster-only, so there is no CLI escape hatch (the warning text says "pass force=True", which is only reachable from the Python API).
Suggestion
When resolving a mint collision, prefer the file whose name matches the entity name as the owner — or more generally, prefer a "defining" page over a "mentioning" one — rather than first-extracted-wins. Failing that, exposing a --force flag on label / cluster-only would at least let users recover a stale report.
Environment
- graphify 0.9.53 (
pipx install graphifyy, plus pipx inject graphifyy openai for the gemini backend)
- macOS, Python 3.14.7
- Backend: gemini
- Corpus: ~60 markdown docs, no code files
Summary
When two files mention the same entity, graphify keeps the node from whichever file was extracted first and drops the other:
The practical effect is that the page that actually defines a concept can lose its own node, and the surviving node is attributed to a file that merely mentions the concept in passing.
Concrete case
Corpus: an Obsidian vault of ~60 markdown notes,
graphify extract wiki/ --mode deep.concepts/작업 단위 폴더 + README 진입점 컨벤션.md(filename == concept name)concepts/LLM Schema - CLAUDE.md를 가이드로 활용하기.mdsource_fileingraph.jsonThe concept itself is in the graph with 4 edges — only the ownership is wrong.
Why it matters
The documented navigation pattern is "read
GRAPH_REPORT.md→ identify relevant nodes → fetch only those source files". Whensource_filepoints at a page that merely mentions the concept, that workflow routes the reader to the wrong file. The authoritative page is invisible to the graph.The documented workaround does not apply here
The warning suggests:
Both files above live in the same folder (
concepts/), so splitting by subfolder cannot separate them.Post-hoc correction is also blocked
Rewriting
source_fileingraph.jsonto point at the defining page does not work, because node ids are re-derived from it —build.py::_semantic_id_remap:So rewriting
source_filecauses ids to be re-derived, collide with other nodes, and get silently merged — reproducing the very problem. The resulting graph shrank 146 → 145 nodes, which then tripped the shrink guard inexport.py:Both
graphify labelandgraphify cluster-onlyrefuse to write, soGRAPH_REPORT.mdcan no longer be regenerated and goes stale.GRAPHIFY_FORCE=1is honored byextractbut not bylabel/cluster-only, so there is no CLI escape hatch (the warning text says "pass force=True", which is only reachable from the Python API).Suggestion
When resolving a mint collision, prefer the file whose name matches the entity name as the owner — or more generally, prefer a "defining" page over a "mentioning" one — rather than first-extracted-wins. Failing that, exposing a
--forceflag onlabel/cluster-onlywould at least let users recover a stale report.Environment
pipx install graphifyy, pluspipx inject graphifyy openaifor the gemini backend)