Repository navigation
C#: CALLS edge attaches to wrong same-name class variant (shared namespace root) — using-directive scoping ignored; trace_path inbound returns callers_total: 0 #2120
Description
Activity
Additional data from the same private repo (46,186 nodes / 121,081 edges), a second symbol family, and a minimal-repro extension. Namespaces anonymized as in the report.
Three distinct failure modes on the real repo — not one
Call as written in source CALLS edge target Correct? UserIdentityInfo.ParseFromClaim×3 (BackOffice prod, plainusing)Contoso.Platform.Identity.UserIdentityInfono — wrong variant BackOfficeIdentityInfo.ParseFromClaim×4 (using BackOfficeIdentityInfo = Contoso.Platform.BackOffice.Identity.UserIdentityInfo;)Contoso.Platform.Identity.PlayerIdentityInfo.ParseFromClaimno — a class with a different simple name, which has zero real callers (verified by grep) PartnerIdentityInfo.ParseFromClaim×4Contoso.Platform.Partner.Identity.PartnerIdentityInfoyes Second family,
UserClaimPrincipleFactory(3 variants: Platform / BackOffice / Partner):new UserClaimPrincipleFactory(...)then.CreateAsync(...)— the Partner one, resolved by a plainusing— attached toContoso.Platform.…UserClaimPrincipleFactory.CreateAsync. Same root-namespace preference. Interestingly the constructor/type edge on the same statement resolved correctly to the Partner variant; only the method call went wrong.- Three
.CreateAsync(...)calls made through a using alias (BackOfficeClaimsFactory) produced no CALLS edge at all.
Inferred rule
The resolver appears to match the textual receiver identifier against class simple names rather than binding it:
- exactly one match → correct (
PartnerIdentityInforesolves fine in the very same file where the alias calls go wrong) - multiple matches → the shortest / root namespace wins, using-directives ignored
- zero matches (alias) → either an arbitrary node carrying the same method name, or no edge
Severity note for the false-positive direction: the phantom callers landed on
PlayerIdentityInfo.ParseFromClaim, which has zero real callers in the repo. Dead-code analysis would report it as live.Breadth in this codebase — classes sharing a simple name across projects:
Handler×104,Request×47,Result×26,Session×6,Login×6,IdentityExtensions×4. This is the default shape of an ASP.NET/CQRS solution, not a corner case.Minimal-repro extension (verified just now)
I extended the two-project repro from the report with a third class and an alias caller:
// src/Contoso.Platform/Identity/PlayerIdentityInfo.cs namespace Contoso.Platform.Identity; public class PlayerIdentityInfo { public static PlayerIdentityInfo ParseFromClaim(string claim) => new PlayerIdentityInfo(); } // src/Contoso.Platform.BackOffice/CqrsHandlers/AliasHandler.cs using BOIdentity = Contoso.Platform.BackOffice.Identity.UserIdentityInfo; namespace Contoso.Platform.BackOffice.CqrsHandlers; public class AliasHandler { public string Handle(string claim) { var info = BOIdentity.ParseFromClaim(claim); return info.ToString(); } }
Fresh re-index, then the same Cypher:
AliasHandler.Handle → …Contoso.Platform.BackOffice.Identity.UserIdentityInfo…ParseFromClaim (correct) Handler.Handle → …Contoso.Platform.Identity.UserIdentityInfo…ParseFromClaim (wrong — the original bug, still)Two observations from this:
- The plain-
usingmis-attachment is stable across re-indexes and file additions —Handler.Handlekeeps landing on the root-namespace variant. - In the minimal shape the alias actually resolved correctly, so the phantom-edge mode from our real repo (alias call landing on a same-method-name class, or producing no edge) did not reproduce in isolation — it may require additional shape (several
ParseFromClaimcall sites in one file, registration order, etc.). Reporting it as observed-but-not-minimized.
One more instability note: in an earlier run of the same repro before the alias file was added, the single plain-
usingcall also landed on the wrong variant — so plain-usingbreakage does not depend on the alias file being present.- addedcypherCypher query language parser/executor bugsCypher query language parser/executor bugsparsing/qualityGraph extraction bugs, false positives, missing edgesGraph extraction bugs, false positives, missing edges
on Sep 9, 2026 - 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 removedcypherCypher query language parser/executor bugsCypher query language parser/executor bugs
on Sep 9, 2026 I tried to reproduce this against current main (055fbb7) in a clean container: built from source, laid out the exact two-project repro from this issue (Contoso.Platform / Contoso.Platform.BackOffice, Handler.cs with the plain
using Contoso.Platform.BackOffice.Identity;), and ran the same index_repository + query_graph commands.Result on this HEAD, 5 fresh re-indexes in a row:
Handle tmp-repro2120.src.Contoso.Platform.BackOffice.Identity.UserIdentityInfo.UserIdentityInfo.ParseFromClaimThat is the correct (BackOffice) target, not the root-namespace one from the report. I also padded the repro to 63 files to force the parallel indexing path (MIN_FILES_FOR_PARALLEL is 50), in case this only shows up at scale, and got the same correct result every time.
One data point that might explain the gap: the issue says version 0.10.8 (AUR), which tags to 2026-08-18. Between that release and current main, PR #1897 landed a receiver-chain guard in src/pipeline/registry.c that fixed a related fabricated-edge shape (I ran into it separately on #2121). I could not find any commit touching internal/cbm/lsp/cs_lsp.c in that same range, so if #1897 is why this now resolves correctly, it would be a side effect through the generic fallback path rather than a direct fix to the C# resolver.
Not clearing this since I can't confirm it's actually fixed rather than just not reproducing in my setup, but wanted to flag that current main behaves correctly on the exact repro given here.
Thank you so much, @ersintarhan, for this report. The minimal two-project repro, the alias extension and the breadth numbers made this very easy to pin down. Thanks also to @AmirF194 for testing against main; your "correct on main" result was real, just order-dependent.
The root cause: C# node names in the graph are built from file paths, not namespaces, so the resolver's namespace and
usinglookups never matched a project type, and among same-named classes the one registered first won. It was never yourusingthat was wrong.The fix (#2347) records each type's declared namespace and binds names the way C# does: enclosing namespaces first, then
usingdirectives; fully-qualified names andusing X = Ns.Typealiases work too. It also treatspartialpieces and reference-assembly copies of one type as the same type, so a method declared in another file of a partial class is still found. On dotnet/runtime this moved about 6,200 CALLS edges from classes the caller cannot see to the ones it can, and added about 8,400 correct edges into partial classes.Once the fix ships, please reindex from scratch (delete the project and index again) so existing edges are recomputed. Thanks again!
Reacted by Amir Fathi and Ersin TarhanVerified on the original repo from this report (the private one — now 53,310 nodes / 154,398 edges), built from #2347 head
b220ecec, fresh from-scratch index.The same-name families called out here now resolve correctly, verified against the callers' actual
usingdirectives:UserClaimPrincipleFactory(BackOffice / Platform / Partner): Platform variant has 0 callers (it no longer "gains" the production calls), Partner and BackOffice each get their own real ones.IdentityUserStorage(3 variants): 10 / 1 / 8 callers, each set matching the variant's project.IdentityExtensions(4 variants incl. thePlatform.Database.Models.Identityone): 27 / 33 / 8 / 3 — every caller set scoped to its own namespace.
The nastiest mode from my earlier comment — same file, plain
using+ using-alias — is also fixed.IdentityContractTests.cshas bothusing Platform.Partner.Identity.ClaimPrincipleFactories;andusing BackOfficeClaimsFactory = ...BackOffice...UserClaimPrincipleFactory;: the plainnew UserClaimPrincipleFactory(...)calls bind to the Partner variant, theBackOfficeClaimsFactoryalias calls bind to BackOffice. Source-verified line by line. On 0.10.8 these were the no-edge / phantom-edge cases.Thanks @DeusData — happy to close once this ships. (Will reindex from scratch per the release note.)
Reacted by Martin VogelFull-repo sweep follow-up (all 2,735 CALLS edges into same-name families, adjudicated against source): the modes reported here — plain
using, using-alias, phantom variant absorbing calls — are fixed by #2347 ✅. The sweep did surface three residual mis-binding patterns (~125 edges, source-verified, incl. nested-type constructors likenew Command.Result(...)and chained-constructor receivers likenew GrainA.Handler(dep).Handle(...)losing the receiver). Details and repro sketches: #2347 (comment) — probably worth handling before the release, but they're a strict subset of what was broken on 0.10.8.- added a commit that references this issue
on Sep 30, 2026 - added a commit that references this issue
on Oct 2, 2026 Fixed on main in 50d19f8 (#2347). Thank you very much, @ersintarhan: the report itself, and then your verification on the original repository and the full sweep of all 2,735 CALLS edges into same-name families, gave this fix a check no synthetic fixture could.
C# types now carry their declared namespace, and a type name binds to the declaration visible from the caller in C# lookup order: enclosing namespaces innermost outward, then
usingnamespaces, withusingaliases bound the same way. It ships in the next release; existing indexes need a reindex.The three residual patterns your sweep found are not part of this merge. Your follow-up #2401 fixes the two type-name-resolution shapes and traces the third to other layers. With #2347 on main, it can now be reviewed against main directly.
- added a commit that references this issue
on Oct 10, 2026
Version
codebase-memory-mcp 0.10.8
Platform
Linux (x64)
Install channel
AUR
Binary variant
standard
What happened, and what did you expect?
When two projects contain classes with the same simple name in namespaces that share a common root (
Contoso.Platform.Identity.UserIdentityInfovsContoso.Platform.BackOffice.Identity.UserIdentityInfo), a call from BackOffice code — with an explicitusing Contoso.Platform.BackOffice.Identity;— gets its CALLS edge attached to the wrong variant (Contoso.Platform.Identity).Consequences:
trace_path --direction inboundon the correct (BackOffice) variant returnscallers_total: 0— a false "unused" signal.trace_path/Cypher on the wrong (Platform) variant reports callers that never call it — a false positive.Expected: Roslyn binding rules — the file's using-directive (and enclosing-namespace proximity) resolve
UserIdentityInfoto the BackOffice variant; the CALLS edge should target that node.On the real repository where we found this (private, ~46,186 nodes / 121,081 edges / ~2,500 files): 3 production callers + 6 test callers of the BackOffice variant all came back
callers_total: 0, while the Platform variant "gained" the 3 production callers.Reproduction
Dummy snippet (two SDK-style projects + three files):
src/Contoso.Platform/Identity/UserIdentityInfo.cs:src/Contoso.Platform.BackOffice/Identity/UserIdentityInfo.cs:src/Contoso.Platform.BackOffice/CqrsHandlers/Handler.cs:Commands:
codebase-memory-mcp cli index_repository --repo-path /tmp/repro --mode full codebase-memory-mcp cli query_graph --project tmp-repro \ --query "MATCH (f)-[:CALLS]->(g) WHERE g.name = 'ParseFromClaim' RETURN f.name, g.qualified_name"Result:
Expected:
Notes from narrowing it down:
Logs
Project scale (if relevant)
Real repo where observed: 46,186 nodes / 121,081 edges / ~2,500 files (1,408 .cs + 1,013 .sql). Repro above: ~dozens of nodes.
Confirmations