You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
diagnose_extraction counts external-package import refs (ref_*) as dangling edges — false "graph may be incomplete/corrupt" warning on JS/TS projects #2191
Step 4.5 of the skill pipeline (graphify.diagnostics.diagnose_extraction) reports:
dangling_endpoint_edges: 217
GRAPH HEALTH WARNING: 217 dangling-endpoint edges - graph may be incomplete/corrupt.
Breaking those 217 edges down by missing endpoint shows they are all AST import references to external npm packages, for which no nodes can exist because node_modules is (correctly) excluded:
So the warning fires on what is arguably every ordinary JS/TS project, and re-extraction cannot fix it — the count stays identical run to run. In our case the LLM-extracted semantic edges were 100% valid; all "dangling" edges came from the deterministic AST layer's external-import refs. We spent a full forced re-extraction chasing this before isolating the cause.
The build layer already handles this correctly (these edges are excluded from graph.json), so the graph itself is fine — only the diagnostic message is misleading.
Steps to reproduce
Any Next.js/React/Strapi repo, run the standard /graphify . pipeline.
Run diagnose_extraction(extraction, directed=False, root='.') on the merged extraction.
Observe dangling_endpoint_edges ≈ (number of external-module import edges), plus the corrupt-graph warning.
Expected / suggested
Classify edges whose missing endpoint matches the external-ref scheme (ref_*, or "endpoint not under scan_root") into a separate counter, e.g. external_reference_edges, excluded from the GRAPH HEALTH WARNING; or
resolve them to synthetic "external package" nodes so they are not dangling at all;
keep the warning for genuinely dangling endpoints (typo'd IDs, cross-chunk drift) — that signal is valuable, it is just drowned out today.
Why this matters for the fix: the suggestion above is to classify "edges whose missing endpoint matches the external-ref scheme (ref_*)". That is a JS/TS-specific shape — it would leave every Python project still emitting the false warning, because pytest and pathlib are indistinguishable from an internal target by name alone. A general fix probably has to be "does this endpoint resolve to a file under scan_root", which works for both ecosystems.
Two supporting details:
The build layer already encodes the correct semantics, so the diagnostic is contradicting it. build.py:952-958:
ifsrcnotinnode_setortgtnotinnode_set:
continue# skip edges to external/stdlib nodes - expected, not an error
and build.py:679-680 deliberately filters these out of error reporting:
real_errors= [eforeinerrorsif"does not match any node id"notine]
exact_duplicate_edges and self_loop_edges have the same reporting problem, in milder form. Mine reported 147 and 1 respectively on an extraction whose built graph contains zero of each — the build removes them. They read as defects when they are already handled.
Cost, echoing the "full forced re-extraction" in the original report: I lost a comparable amount of time, and nearly triggered a ~2.5M-token semantic re-extraction to "fix" a healthy graph.
One extra trap worth documenting, specific to Python: diagnosing from graphify-out/cache/ast/ fragments is actively misleading. They are stored in a pre-resolution encoding ($graphify-root$ marker, cache.py:534; bare dotted-module targets), re-anchored and repointed only after cache load inside extract(). Reading them makes already-resolved internal imports look permanently broken — in my graph.json the bare "evidencehq_models" occurs 0 times while the resolved "src_evidencehq_models" occurs 206, i.e. _repoint_python_package_imports (#2072) had done its job correctly all along. A note in format_diagnostic_report() warning that cache/ast/ numbers are pre-resolution upper bounds would save the next person this detour.
247 of 247 from the deterministic AST layer (unresolved stdlib/third-party call targets); zero from the LLM semantic pass. Identical to your finding.
The practical cost: that 0-vs-247 split is the whole diagnostic signal — "my semantic node IDs are malformed" and "the AST can't see os.path" are completely different problems with the same headline number. I had to write my own script to learn which one I had, and the report gives the alarming total with no way to tell.
So beyond suppressing external refs, it would help if format_diagnostic_report printed the breakdown it already has the data for:
A run where that second number is non-zero is the one actually worth warning about — that means node IDs emitted by the extraction prompt don't match what the AST produced, which is silent corruption.
Activity
alandooley commentedon Aug 2, 2026
Confirming this on the Python path as well — same root cause, but the proposed
ref_*fix would not catch it.Environment: graphifyy 0.9.32, Python 3.13, Windows 11. ~945-file Python repo (704 code / 216 doc / 25 image), 13.8k nodes.
Step 4.5 reported:
Classifying them: ~1,789 of 2,751 (82%) are stdlib/third-party import targets. But on the Python path these are bare module names, not
ref_*-prefixed:Why this matters for the fix: the suggestion above is to classify "edges whose missing endpoint matches the external-ref scheme (
ref_*)". That is a JS/TS-specific shape — it would leave every Python project still emitting the false warning, becausepytestandpathlibare indistinguishable from an internal target by name alone. A general fix probably has to be "does this endpoint resolve to a file underscan_root", which works for both ecosystems.Two supporting details:
The build layer already encodes the correct semantics, so the diagnostic is contradicting it.
build.py:952-958:and
build.py:679-680deliberately filters these out of error reporting:exact_duplicate_edgesandself_loop_edgeshave the same reporting problem, in milder form. Mine reported 147 and 1 respectively on an extraction whose built graph contains zero of each — the build removes them. They read as defects when they are already handled.Cost, echoing the "full forced re-extraction" in the original report: I lost a comparable amount of time, and nearly triggered a ~2.5M-token semantic re-extraction to "fix" a healthy graph.
One extra trap worth documenting, specific to Python: diagnosing from
graphify-out/cache/ast/fragments is actively misleading. They are stored in a pre-resolution encoding ($graphify-root$marker,cache.py:534; bare dotted-module targets), re-anchored and repointed only after cache load insideextract(). Reading them makes already-resolved internal imports look permanently broken — in mygraph.jsonthe bare"evidencehq_models"occurs 0 times while the resolved"src_evidencehq_models"occurs 206, i.e._repoint_python_package_imports(#2072) had done its job correctly all along. A note informat_diagnostic_report()warning thatcache/ast/numbers are pre-resolution upper bounds would save the next person this detour.cdtatarevich commentedon Aug 16, 2026
Corroborating data point from a Python corpus — same conclusion, different language, so this is not JS/TS-specific.
Env: graphifyy 0.9.39, macOS, 116-file mixed corpus (74 code / 32 docs / 10 images), 1272 nodes / 2707 edges.
Step 4.5 reported:
Splitting them by producer:
247 of 247 from the deterministic AST layer (unresolved stdlib/third-party call targets); zero from the LLM semantic pass. Identical to your finding.
The practical cost: that 0-vs-247 split is the whole diagnostic signal — "my semantic node IDs are malformed" and "the AST can't see
os.path" are completely different problems with the same headline number. I had to write my own script to learn which one I had, and the report gives the alarming total with no way to tell.So beyond suppressing external refs, it would help if
format_diagnostic_reportprinted the breakdown it already has the data for:A run where that second number is non-zero is the one actually worth warning about — that means node IDs emitted by the extraction prompt don't match what the AST produced, which is silent corruption.
safishamsi commentedon Sep 20, 2026
Fixed in v0.9.64 via #3590 — diagnostics separate external references from dangling edges. Thanks @DevChiniwala.