Skip to content

Cross-file call resolver misattributes calls to wrong same-named function (_make_id example) #2945

Description

@TotalUnitManagement

Summary

The cross-file calls-edge resolver misattributes calls to the wrong function when two files define a same-named helper and only one of them is actually imported by the caller. Found while auditing a /graphify . run over this repo's own graphify/ source tree — one of the reported "god nodes" (_make_id(), 54 edges) turned out to include 27 misattributed edges.

Root cause

There are two independent definitions of _make_id:

  • graphify/extractors/base.py:54def _make_id(*parts): return make_id(*parts)
  • graphify/mcp_ingest.py:379def _make_id(*parts): return _shared_make_id(*parts) (comment: "kept local; mirror extract.py shape")

graphify/extract.py imports _make_id only from graphify.extractors.base (line 31: from graphify.extractors.base import (..., _make_id, ...)) and defines no local override. All 84 _make_id(...) call sites inside extract.py should therefore resolve to extractors/base.py's _make_id.

Instead, the built graph attributes 27 of those calls — from functions including extract(), extract_astro(), extract_csproj(), extract_lazarus_package(), extract_python_rationale(), extract_slnx(), extract_spock_fallback(), extract_svelte() — to mcp_ingest.py's _make_id instead, and marks these edges EXTRACTED (the highest confidence tier), i.e. confidently wrong rather than a low-confidence guess.

Meanwhile, the 52 correctly-resolved cross-file callers of extractors/base.py's _make_id (from the various language extractors, e.g. extract_apex(), extract_bash(), extract_dm(), extract_elixir()) are all marked INFERRED — so the resolver is inconsistent even on the correctly-resolved side (see the separate, likely-related observation below).

Repro

  1. graphify . (or a scoped subfolder) on a checkout of Graphify-Labs/graphify itself, targeting graphify/ as the corpus.
  2. Inspect graphify-out/graph.json for edges where target == "mcp_ingest_make_id".
  3. Cross-reference each edge's source node's source_file against that file's actual import statements.
  4. 27 of them (all functions defined in extract.py) point at mcp_ingest_make_id despite extract.py never importing anything from mcp_ingest named _make_id.
import json
g = json.load(open("graphify-out/graph.json", encoding="utf-8"))
edges = g.get("links", g.get("edges", []))
callers = [e for e in edges if e.get("target") == "mcp_ingest_make_id" and e.get("relation") == "calls"]
print(len(callers))  # 27, all sourced from extract.py functions

Impact

  • Silently wrong provenance for calls edges whenever two files define a same-named helper and that name is called cross-file from a third location — the resolver appears to pick a candidate without checking the caller's actual import statement.
  • Made worse by confidence labeling: the wrong edges are EXTRACTED (implying certainty), while the correct edges to the same target function (from files that genuinely do import it) are INFERRED — so confidence tier doesn't correlate with correctness here.
  • Scope: only affects codebases with duplicate function names across files where at least one of the duplicates is called from a third file (not just within the two defining files). In this repo, checked all 11 duplicate function names (load_graph, _node_community_map, main, _strip_diacritics, _yaml_str, _git_head, install, _safe_filename, _make_id, _nfc, __getattr__) — _make_id was the only one with cross-file (third-party) callers, so it's the only one exhibiting the bug in this corpus. Likely to recur in any codebase with a similarly widely-used, ambiguously-named helper.

Suggested fix direction

When resolving a bare-name call to a symbol that has multiple same-named definitions across files, check the caller's actual import statements (from module import name) before falling back to a heuristic/positional match. If the caller's file has an unambiguous from X import name for that exact symbol, resolve to X's definition with EXTRACTED confidence rather than guessing.

Environment

  • graphify package version: 0.9.48 (repo pyproject.toml at time of testing)
  • Corpus: graphify/ subfolder of this repo (82 code files)
  • Extraction mode: default (AST-only, code-only corpus, no LLM semantic pass involved — this is a pure structural/AST resolution bug, not an LLM extraction issue)

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