Skip to content

diagnose_extraction counts external-package import refs (ref_*) as dangling edges — false "graph may be incomplete/corrupt" warning on JS/TS projects #2191

Closed

Description

@jj954081-dotcom

Environment

  • graphifyy 0.9.26, Python 3.12.10, Windows 11
  • Project: pnpm monorepo — Next.js 16 storefront + Strapi 5 CMS, 230 code files (TS/TSX), node_modules excluded (default)

What happens

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:

 58  ref_strapi_strapi      (@strapi/strapi)
 39  ref_next
 38  ref_next_link
 33  ref_react
 13  ref_next_navigation
  4  ref_next_headers
  3  ref_path
  3  ref_strapi_strapi_admin
 ...

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

  1. Any Next.js/React/Strapi repo, run the standard /graphify . pipeline.
  2. Run diagnose_extraction(extraction, directed=False, root='.') on the merged extraction.
  3. 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.

Activity

  1. alandooley commented on Aug 2, 2026

    @alandooley

    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:

    dangling_endpoint_edges: 2841
    GRAPH HEALTH WARNING: 2841 dangling-endpoint edges; ... - graph may be incomplete/corrupt.
    

    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:

     373  pytest
     202  pathlib
     117  typing
     100  json
      81  dataclasses
      80  logging
      59  re
      56  sys
      55  httpx
      51  fastapi_testclient
    

    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:

    1. The build layer already encodes the correct semantics, so the diagnostic is contradicting it. build.py:952-958:

      if src not in node_set or tgt not in node_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 = [e for e in errors if "does not match any node id" not in e]
    2. 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.

  2. cdtatarevich commented on Aug 16, 2026

    @cdtatarevich

    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:

    dangling_endpoint_edges: 247
    GRAPH HEALTH WARNING: 247 dangling-endpoint edges - graph may be incomplete/corrupt.
    

    Splitting them by producer:

    total dangling 247
    [('agent_reach/transcribe.py', 10), ('tests/test_doctor.py', 8),
     ('tests/test_opencode_skill.py', 8), ('tests/test_private_file_writes.py', 8),
     ('tests/test_skill_command.py', 8), ('tests/test_v2ex_channel.py', 8), ...]
    dangling from semantic chunk: 0
    

    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:

    dangling_endpoint_edges: 247  (247 AST / 0 semantic)
    

    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.

  3. safishamsi commented on Sep 20, 2026

    @safishamsi
    Member

    Fixed in v0.9.64 via #3590 — diagnostics separate external references from dangling edges. Thanks @DevChiniwala.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions