Repository navigation
json_config extractor emits imports/extends edges to node IDs it never creates (package.json, tsconfig.json) #1764
Description
Summary
extract_json emits imports and extends edges whose target node is never created. build_from_json then drops those edges without a warning, because the "does not match any node id" error is explicitly filtered out of real_errors. The result is silent edge loss on two of the most common files in any JS/TS repo: package.json and tsconfig.json.
Nothing surfaces this except graphify.diagnostics.diagnose_extraction, which reports them as dangling_endpoint_edges — and by then the edges are already gone from graph.json.
Version
Reproduced on 0.9.11. Python 3.14.
Reproduction
Two files in an empty directory:
// package.json
{
"name": "demo",
"dependencies": { "left-pad": "^1.3.0" },
"devDependencies": { "bats": "^1.11.0" }
}// tsconfig.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": { "strict": true }
}from pathlib import Path
from graphify.extract import extract
for f in ("package.json", "tsconfig.json"):
r = extract([Path(f)], cache_root=Path("."))
ids = {n["id"] for n in r["nodes"]}
dangling = [e for e in r["edges"] if e["source"] not in ids or e["target"] not in ids]
print(f, len(r["nodes"]), "nodes", len(r["edges"]), "edges", len(dangling), "dangling")
for e in dangling:
print(" ", e["source"], "--" + e["relation"] + "-->", e["target"])Actual
package.json 6 nodes 7 edges 2 dangling
package_dependencies_left_pad --imports--> left_pad
package_devdependencies_bats --imports--> bats
tsconfig.json 4 nodes 4 edges 1 dangling
tsconfig --extends--> ref_tsconfig_base_json
Nodes left_pad, bats, ref_tsconfig_base_json do not exist. At build time all three edges disappear from the graph and no warning is printed.
Cause
graphify/extractors/json_config.py:
# ~line 176 — `extends`
ref_nid = _make_id("ref", val_text)
if ref_nid:
add_edge(file_nid, ref_nid, "extends", line, context="import")
# line 187-189 — dependency keys
elif parent_key in _DEP_KEYS and val_text:
dep_nid = _make_id(key)
if dep_nid:
add_edge(key_nid, dep_nid, "imports", line, context="import")grep -n 'dep_nid' extractors/json_config.py returns only those three lines: the target ID is synthesized and used as an edge endpoint, but add_node is never called for it. Same for ref_nid.
The drop is then made invisible in graphify/build.py:
real_errors = [e for e in errors if "does not match any node id" not in e]so the endpoint-mismatch never reaches the user.
(The $ref branch a few lines above uses the same _make_id("ref", val_text) pattern and looks like it shares the defect, but I could not exercise it: my minimal JSON-Schema test file produced zero nodes, which is #1666. Flagging it as suspected-by-inspection, not verified.)
Expected
Either of these would be self-consistent; today's behaviour is neither:
- Create the node. Emit an explicit external node for the dependency / extended config (e.g.
file_type: "concept", labelleft-pad), so theimports/extendsedge lands. This is precisely the resolution already adopted for the JS/TS equivalent in JavaScript namespace re-exports emit dangling import targets #1551 ("Create a real namespace binding … and resolve the consumer import to it"), so it would makejson_configconsistent with the rest of the pipeline. - Do not emit the edge. If, after Cross-language phantom edges: unresolved imports/references bind by name across language boundaries #1749, the intent is that unresolved external references must never bind to a node, then the extractor should simply not create the edge.
Given the #1551 precedent, option 1 looks right. Dependency and extends edges are genuinely useful — they are what most users would expect a package.json / tsconfig.json to contribute to the graph.
Related issues
- JavaScript namespace re-exports emit dangling import targets #1551 (closed) — JavaScript namespace re-exports emit dangling import targets. Same defect class in the JS/TS extractor; fixed by creating the missing node. This report is the
json_configinstance of it. - [Bug] build_merge severs edges from UNCHANGED files into re-extracted files (−12% doc↔code in one update cycle): endpoint nodes are replaced, the surviving edges dangle, and the dangling-edge drop silently eats them #1711 (open) — documents the silent half: dangling edges are dropped without a warning. The
real_errorsfilter quoted above is that mechanism. This issue is about the extractor producing the dangling edge in the first place; fixing [Bug] build_merge severs edges from UNCHANGED files into re-extracted files (−12% doc↔code in one update cycle): endpoint nodes are replaced, the surviving edges dangle, and the dangling-edge drop silently eats them #1711 would make it visible, fixing this would make it unnecessary. - JSON extractor: npm package.json dependency-block keys appear as god-nodes #903 (closed) — dep-block keys surfacing as god-nodes. Different problem (god-node selection,
_JSON_NOISE_LABELS), same file. Not a duplicate: that issue is about nodes that exist and rank too high, this one about a node that never exists.
Same pattern elsewhere
Two other extractors synthesize an edge endpoint without creating the node:
extractors/bash.py:185—source <non-path-word>→add_edge(file_nid, _make_id(raw), "imports", ...)extractors/objc.py:169—add_edge(file_nid, _make_id(module), "imports", ...)
Worth fixing as one family.
Impact
Every repository with a package.json or tsconfig.json silently loses its dependency edges.
Concretely: on a 63-file repo I audited, diagnose_extraction reported 49 dangling-endpoint edges. I was able to eliminate 47 of them by re-extracting the affected documents. The remaining two were these package.json imports edges — unfixable from the corpus side, because no input to graphify can make the extractor create the node it points at.
Activity
oleksii-tumanov commentedon Jul 10, 2026
I opened a focused PR for the confirmed
json_configcases:package.jsondependency targets andtsconfig/jsconfigextendstargets.I kept Bash/ObjC separate because they touch different extractors and may need extractor-specific review.
fix(extract): create json_config reference target nodes (#1764)
safishamsi commentedon Jul 10, 2026
Fixed on v8 (0.9.12), credited to @oleksii-tumanov via #1767. extract_json now creates the referenced dependency/extends/$ref targets as
conceptnodes before emitting the edges, so package.jsonimportsand tsconfig.jsonextendsedges survive build instead of being silently dropped. Thanks for the precise repro.fix(extract): create json_config reference target nodes (Graphify-Lab…
test: track the manifest-sourced dep edge after the 0.9.55 rebase
test: track the manifest-sourced dep edge after the 0.9.55 rebase
fix(json_config): link a package.json dependency's manifest entry to …