Repository navigation
TypeScript: cross-file method calls produce no CALLS edge — type-aware tier resolves same-file calls only, so trace_path(inbound) returns 0 callers #1354
Description
Activity
- addedparsing/qualityGraph extraction bugs, false positives, missing edgesGraph extraction bugs, false positives, missing edgeswindowsWindows-specific issuesWindows-specific issues
on Jul 30, 2026 Correcting one claim in the body that is false, and adding a control that makes the case much sharper. The headline finding stands.
Correction: the type-aware tier is not same-file-only
I wrote that "the type-aware
lsp_ts_*tier appears to only ever resolve same-file calls". That's wrong, and it's refuted by data I already had in the issue:lsp_ts_import: 80 edges, 80 of 80 cross-file. Cross-file type-aware resolution works fine for imported free functions.lsp_ts_method: 3,049Method→Methodedges, of which 4 are cross-file (0.13%).
So "zero of the 1,017 cross-file edges came from
lsp_ts_method" was a sampling artifact of myLIMIT; on the full population it's 4, not 0. The accurate and narrower claim:Cross-file resolution works for imported function symbols. It collapses specifically for member calls on instances of imported classes —
lsp_ts_methodis 99.87% same-file.Full-population re-derivation (all 15,837
CALLSedges, no sampling):Method→Methodsame-file 3,080 of whichlsp_ts_method3,045; cross-file 1,705 —qualified_suffix663,unique_name557,import_map257,suffix_match215.Added to the repro: a same-file control, which I think is the strongest form of this
I've pushed a control to the repro repo.
sameFileControl()intoast.service.tsis character-for-character the same shape asv2, just located in the callee's own file:export function sameFileControl(): void { const t = new ToastService(); t.openSuccessUniqueXyz('same-file control'); }
caller location relative to callee edge to the method? sameFileControlsame file ✅ lsp_ts_method, confidence 0.95v2— identical codeanother file ❌ none v1,v3,v4,V5.run,AliasConsumer.runanother file ❌ none $ trace_path --function-name openSuccessUniqueXyz --direction inbound --depth 1 callers_total: 1 cbm-rc-repros.src.lib.toast.toast.service: sameFileControl 1Identical code, one file boundary, and the edge disappears.
Possibly the same root cause as #1277, and possibly the cause of #514
- Python cross-file receiver inference loses typed instance fields #1277 ("Python cross-file receiver inference loses typed instance fields") looks like the same defect in the Python extractor — if the mechanism there is that instance-field types aren't exported into the cross-file definition contract, that would explain this exactly.
- trace_path data_flow mode doesn't surface arg expressions; NestJS DI patterns defeat ~70% of caller resolution #514 (NestJS DI patterns defeat
trace_path, incomplete caller sets) is plausibly a symptom of this rather than an independent bug — DI-injected services are precisely "member call on an instance of an imported class".
Two leads for whoever picks this up
1. The cross-file path is flaky, not absent. All four working cross-file
lsp_ts_methodedges come from a single call site shape: a private field declared as a union of two imported classes (private _h: AHandler | BHandler, assignednew AHandler()ornew BHandler()in the constructor), then called asthis._h.method(...). That is exactly the shape this issue says never resolves. And only one union branch got edges — the four methods onBHandlereach have exactly 1 inbound edge, while every method on the siblingAHandlerhas 0 inbound edges of any strategy. So there is a working code path here that fires inconsistently, which may be a more tractable entry point than "add cross-file support".2. A guard that should already be preventing the #1355 false positives isn't firing.
cbm_tsjs_suppress_weak_method_match(is_tsjs, is_method, strategy)(src/pipeline/registry.c, called frompass_calls.candpass_parallel.c) is asserted intests/test_registry.cto return true for("unique_name", is_method=true)— yet the cross-fileMethod→Methodpopulation contains 557unique_name+ 215suffix_matchedges. Either those call sites aren't being flaggedis_method, or the guard isn't reached on that path. That single site would explain both the missing edges here and the fabricated ones in #1355.Finally, an unrelated nit found while verifying: with
--include-tests true, thetestcolumn/marker thattrace_path --helppromises ("test nodes are included with a test column/marker") is absent from the output entirely, even when the sole caller is a.spec.tsfile.
Posted by Claude Code on behalf of @artaommahe
- addededitor/integrationEditor compatibility and CLI integrationEditor compatibility and CLI integration
on Jul 30, 2026 Independent corroboration from a different codebase, plus a version delta that may help size the impact.
Same symptom, different stack. TypeScript/Express backend (not Angular), 2,250 files, 25,269 nodes / 59,596 edges, 0.9.0 release binary, macOS arm64. The dominant shape here is a service instantiated in a route handler:
const resolver = new EntitlementResolverService(supabase); const features = await resolver.resolveFeatures(userId, leagueId);
resolveFeatureshas two real route callers by grep. In the graph it hasin_degree: 0, andtrace_path(inbound)returnscallers: []. As in your Case A, the only edge recorded is thenew EntitlementResolverService()constructor call. This holds for both module-scope and function-local instantiation of the service.Aggregate for the same repo: 71
CALLSedges frombackend/src/routes→backend/src/services, and when you print them the sources are file nodes and the targets are class names — i.e. essentially all 71 arenew XService()constructor edges, not handler→method edges. A sample of six well-known service methods (useMulligan,submitLineup,modifyLineup,createContest,getLiveLeaderboard,mintCode) has zero inbound edges from the routes directory.Version delta — 0.8.1 vs 0.9.0, identical source tree. A second machine here runs 0.8.1 against the same commit. Edge totals:
edge type 0.8.1 0.9.0 delta CALLS 16,892 10,509 −6,383 (−38%) IMPORTS 2,265 3,460 +1,195 DEFINES 22,028 22,386 +358 USAGE 15,365 15,687 +322 (all others) ±9 or less total 64,040 59,596 −4,444 CALLSis the entire delta; the smaller gains elsewhere partially mask it in the headline edge count. On 0.8.1 theresolveFeaturesroute edges do exist (3 callers, viasuffix_match/field_type_hint) — they are absent on 0.9.0.So on this repo the cross-file method-call gap looks less like a long-standing limitation and more like a 0.9.0 change: the name-based fallbacks that were covering these calls on 0.8.1 appear to no longer fire, while the type-aware tier still doesn't resolve cross-file — leaving nothing. That's consistent with your finding that zero of 1,017 cross-file edges came from
lsp_ts_method; it suggests the cross-file cases were only ever served by the heuristic tiers, so any tightening there removes them outright.Caveat on provenance: I ran the 0.9.0 figures myself; the 0.8.1 column is from the second machine, same tree, not re-run by me.
Happy to run diagnostics against either index if useful.
(Filed alongside three separate 0.9.0 issues from the same investigation — #1364, #1365, #1366 — which are distinct from this one.)
- addedbugSomething isn't workingSomething isn't workingpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.and removedwindowsWindows-specific issuesWindows-specific issues
on Aug 3, 2026 Thank you for the cross-file versus same-file control and the 0.8.1 to 0.9.0 edge-count comparison. That evidence points to a TypeScript resolution regression rather than absent source extraction. I have removed the unrelated Windows label and routed this as a high-priority
0.9.1-rcparsing/call-graph bug; the red case should require the cross-file method edge while preserving the same-file behavior.Reacted by Maksim PopovIndependent confirmation on current stable v0.10.8, macOS arm64, standard GitHub release binary.
A React Native TypeScript project has a cross-file call shaped like this:
type Service = ReturnType<typeof createService>; export function useAction(service: Service) { return (payload: Payload) => service.updateData(payload); }
The target method is defined on the object returned by the factory in another file.
trace_pathwith inbound direction returnscallers_total: 0and an empty caller set, while a focused source search finds the direct call. The graph was current, the canonical checkout was clean, and there were no stale-index or parse-error symptoms.There is an additional completeness-signal gap:
check_index_coveragefor the cited paths returnedmetadata_match/no_recorded_issuedespite the missingCALLSedge. This can make consumers interpret an empty trace as proof of absence.Expected: the cross-file member call produces a
CALLSedge, or the tooling reports that this relationship is unresolved/incomplete.Actual: the trace is silently empty while coverage looks healthy.
This appears to be the same regression, not a separate issue. No proprietary code, identifiers, or paths are included here.
Thank you again for reporting this, @artaommahe! The fix is now on
mainin 77ea3bb (merged via #2407), and it will ship in the next release.
Version
codebase-memory-mcp 0.9.1-rc.1
Platform
macOS (Apple Silicon)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
In TypeScript, a method call on an instance of a class defined in another file produces no
CALLSedge — in any call shape I could construct. Only thenew X()constructor call is recorded.trace_path(direction: 'inbound')therefore reportscallers_total: 0for methods that demonstrably have callers, which makes "who calls X" unusable for the dominant shape in OO/Angular TypeScript (this.service.method()).The type-aware
lsp_ts_*tier appears to only ever resolve same-file calls. Sampling 3,000Method -[:CALLS]-> Methodedges on a ~11.8k-file Angular monorepo:lsp_ts_method1,957 (98.7%)qualified_suffix409,unique_name319,import_map145,suffix_match144Zero of the 1,017 cross-file edges came from
lsp_ts_method. So correct cross-file call edges are absent, and the cross-file edges that do exist are all name-based guesses (see the companion issue on misattribution).Expected:
t.someMethod()wheret: SomeClassandSomeClassis imported from another file should produce aCALLSedge toSomeClass.someMethod, resolved by the type-aware tier.Notably not an import-resolution problem: this reproduces identically for a
tsconfig.jsonpathsalias import and a plain relative import, and with or withouttypescriptinstalled innode_modules. (Mentioning because #730 / #767 / #1085 are closed and alias-specific.)Reproduction
Public repro repo: https://github.com/artaommahe/codebase-memory-mcp-rc-repros — see Case A.
src/app/variants.tsandsrc/app/alias-consumer.tscontain six calls toToastService.openSuccessUniqueXyz(a uniquely-named method insrc/lib/toast/toast.service.ts) across five distinct call shapes, five via relative import and one via the@lib/*alias.Observed:
Every
CALLSedge in the whole graph:codebase-memory-mcp cli query_graph --project cbm-rc-repros \ --query "MATCH (a)-[r:CALLS]->(b) RETURN a.file_path AS caller_file, a.name AS caller, b.qualified_name AS callee, r.strategy AS s, r.confidence AS conf, r.line AS line LIMIT 20"Six real calls to
openSuccessUniqueXyz, zero edges to it. The only edges recorded arenew ToastService()constructor calls, and even those resolve viaunique_namerather than the type-aware tier.v4(explicitly typed parameter),V5.run(typed class field) andAliasConsumer.runproduce no edges at all.Project scale (if relevant)
Aggregate figures above are from a private Angular monorepo: 11,788 files indexed, 63,840 nodes / 120,805 edges,
--mode full, 10,175.tsfiles. The linked repro is self-contained dummy code.Confirmations
Posted by Claude Code on behalf of @artaommahe