Skip to content

Python stdlib import binds to a same-named TypeScript function (cross-language, strategy=unique_name) — poisons hotspots and Louvain clusters #1572

Description

@SQLBImhugh

Environment: v0.10.2, Windows 11, daemon mode. Reproduced on a 2-file synthetic repo and on a real Python+TypeScript monorepo (~41.8k nodes / ~198.8k edges, --mode full).

Symptom

A Python from unittest.mock import patch binds to a TypeScript function named patch, in a different language, in a different top-level package. Every with patch(...) in the repo is then recorded as a CALLS edge into that TSX function.

In my real repo this produced a 4-line React helper with 498 inbound callers, all of them backend Python tests:

// frontend/src/components/gates/reportgate/ReportGatePanel.tsx:151-154
function patch(changes: Partial<ReportGateState>) {
  if (!state) return
  applyEdit({ ...state, ...changes })
}

Minimal reproducer

Two files, no dependencies (24 nodes / 28 edges):

frontend/Panel.tsx

export function Panel() {
  function patch(changes: Partial<State>) {
    applyEdit({ ...changes })
  }
  return patch
}

backend/test_thing.py

from unittest.mock import patch


def test_one():
    with patch("os.getcwd", return_value="/tmp"):
        pass


def test_two():
    with patch("os.getcwd", return_value="/tmp"):
        pass
codebase-memory-mcp cli index_repository --repo-path <repo> --mode fast
codebase-memory-mcp cli trace_path --project <p> --function-name patch \
    --direction inbound --include-evidence true --include-tests true

Result:

function: patch
direction: inbound
callers_total: 2
callers: 2  (rows: name hop strategy confidence)
  test_one 1 heuristic 0.75
  test_two 1 heuristic 0.75

Root cause: the IMPORTS edge is already wrong, and it is not an ambiguity failure

The bad binding happens at the import, not at the call site:

MATCH (a)-[r:IMPORTS]->(b) RETURN a.name, a.file_path, b.qualified_name, r.local_name

  test_thing.py | backend/test_thing.py | <proj>.frontend.Panel.patch | patch

from unittest.mock import patch resolved to a project-local TSX node. The CALLS edges then faithfully follow that import binding.

The edge metadata is the important part:

MATCH (a)-[r:CALLS]->(b) WHERE b.name = 'patch'
RETURN a.file_path, b.file_path, r.strategy, r.confidence, r.candidates

  backend/test_thing.py | frontend/Panel.tsx | unique_name | 0.75 | 1

strategy=unique_name, candidates=1. The resolver is not choosing badly among several candidates — it sees exactly one node named patch in the whole graph and treats that as proof, with no language check and no check that the import specifier (unittest.mock) is external.

This means #1555's proposed fix does not cover this case. That issue asks for better disambiguation when candidates > 1; here candidates == 1, so the ambiguity path is never entered. The two issues are complementary: #1555 is same-language ambiguity, this is a cross-language false positive on a unique name.

Why this is worse than a bad trace hop

The wrong edges silently contaminate the aggregate analyses that agents treat as ground truth:

  1. get_architecture hotspots. The 4-line React helper is reported as the FastAPI empty-path routes not captured #8 hotspot in the repo (fan_in 498), ahead of real infrastructure. Hotspots do not surface confidence, so nothing signals that the entry is fabricated.
  2. Louvain clustering. The node becomes an artificial hub: ~498 backend test functions get pulled into one cluster together with frontend and frontend-mockups, reported at cohesion 0.78. The cluster looks like a genuine architectural seam spanning four packages. It is an artifact.
  3. Dead-code / impact analysis. Anything keying off fan-in inherits the error.

A second instance in the same repo: RuntimeFlagResolver.get reports fan_in 611, whose "callers" are module-level __file__ statements — though that one is at least honestly scored (confidence 0.06, vs 0.95 for the patch edges in the real repo).

Python is especially exposed because stdlib names (patch, get, run, main, setup) collide constantly with short method names in other languages. builtins.* names appear to be handled by a synthetic-node path already; unittest.mock.patch is not a builtin and falls through it.

Suggested fixes

In rough order of value:

  1. Never resolve an import or call across a language boundary. A Python import should not bind to a .tsx node under any strategy. This alone eliminates the whole class.
  2. Treat a resolved import specifier as external when it does not correspond to a project-local module. unittest.mock is not a path in the repo; the specifier is already parsed, since the IMPORTS edge carries local_name. Binding to a synthetic external node (as builtins.* seems to do) would be consistent with existing behavior.
  3. Do not let strategy=unique_name imply high confidence. Uniqueness within the graph is not evidence of a call relationship when the only candidate is in another language/package. Consider capping confidence for cross-package unique_name hits.
  4. Propagate confidence into aggregates. get_architecture hotspots/clusters could exclude or flag edges below a confidence threshold, or expose the weakest strategy contributing to a hotspot. Related: trace_path format=json drops include_evidence strategy and confidence #1542 (strategy/confidence dropped in format=json).

Happy to test a patch against the real repo — the misresolution is deterministic there and easy to re-measure via trace_path --include-evidence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UXwindowsWindows-specific issues

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions