[Bug]: C# nested type identity is truncated to one level, merging distinct methods into one node

Author: merlinhu1Created Aug 30, 2026Updated Sep 16, 2026
Labelsbug

code-review-graph version

2.3.8

Operating system

macOS

Python version

3.12.4

AI platform

claude-code

Output of code-review-graph status

bash
Method nodes inside a nested class are qualified with only the immediate enclosing class, not the full containing-type path. When two nested classes share a name — the standard MediatR-on-ASP.NET feature layout used by gothinkster/aspnetcore-realworld-example-app — their methods collapse into a single node. One method is silently lost. No uncertainty caveat covers this.

Steps to reproduce

mkdir -p /tmp/crg-nested && cd /tmp/crg-nested && git init -q . cat > Features.cs <<'EOF' using MediatR;

namespace App.Articles;

public class Details { public record Query(string Slug) : IRequest;

public class QueryHandler : IRequestHandler<Query, string>
{
    public Task<string> Handle(Query request, CancellationToken ct) => Task.FromResult("details");
}

}

public class Edit { public record Query(string Slug) : IRequest;

public class QueryHandler : IRequestHandler<Query, string>
{
    public Task<string> Handle(Query request, CancellationToken ct) => Task.FromResult("edit");
}

} EOF code-review-graph build python3 - <<'EOF' import sqlite3 c = sqlite3.connect('.code-review-graph/graph.db') for r in c.execute("select kind, qualified_name from nodes where kind!='File' order by qualified_name"): print(f"{r[0]:9} {r[1]}") EOF

Expected vs actual behavior

Expected Features.cs::Details.QueryHandler.Handle and Features.cs::Edit.QueryHandler.Handle as separate nodes.

Actual: Class Features.cs::Details Class Features.cs::Details.Query Class Features.cs::Details.QueryHandler Class Features.cs::Edit Class Features.cs::Edit.Query Class Features.cs::Edit.QueryHandler Function Features.cs::QueryHandler.Handle ← one node for two distinct methods

Additional context

This issue seems to affect more than C#

Cause parser.py:10298, when recursing into a class body

Julia is the only language that accumulates nested scope; every other language passes the bare simple name to _qualify (parser.py:14378). _julia_scope_join (parser.py:7428) already implements the needed join, including its repeated-prefix guard.

Impact

Silent node loss with no caveat. Any tool that addresses a method — callers_of, tests_for, impact radius, review context, refactor/rename — targets a node standing for two unrelated methods. This is the failure mode CRG's uncertainty machinery exists to prevent, and it is not covered.

Second symptom: only one level is ever retained

mkdir -p /tmp/crg-deep && cd /tmp/crg-deep && git init -q . cat > Deep.cs <<'EOF' namespace App; public class A { public class B { public class C { public void M() { } } } } EOF code-review-graph build

Nodes: Deep.cs::A, Deep.cs::A.B, Deep.cs::B.C (should be A.B.C), Deep.cs::C.M (should be A.B.C.M). The containment tree is broken as a result: A.B.C's CONTAINS edge comes from the File rather than from A.B, and the edge Deep.cs::C -> Deep.cs::C.M has a source node that does not exist. Separately, querying by the correctly-qualified nested name does not resolve — code-review-graph query children_of Details.QueryHandler returns target not indexed: no node matching 'Details.QueryHandler' even though that node exists.

Source: tirth8205/code-review-graph