Summary
Via the index_code MCP tool (equivalent to egr index's underlying parser), C# (.cs) source is now parsed on engramgraph@0.7.0 — a real improvement over what we'd observed before. However, we found a silent whole-file parse failure: certain common, valid C# constructs cause index_code to return functions: 0, classes: 0 for the entire file, with no error, warning, or partial result — indistinguishable from "this file legitimately has no functions."
This is worse than "C# unsupported," because it looks like success. A consumer indexing a codebase has no signal that a file silently produced nothing.
Minimal repro
Confirmed via bisection (isolating one variable at a time) on engramgraph@0.7.0, called through the MCP index_code tool.
Fails — returns {functions: 0, classes: 0, ...} for the whole file:
namespace Test;
public sealed class PiiHasherFullIface : ISomeFreshUnusedInterfaceName123
{
private readonly string _version;
private readonly int _tokenLength;
private readonly byte[] _pepper;
public PiiHasherFullIface(string pepper)
{
_version = "v1";
_tokenLength = 8;
_pepper = Encoding.UTF8.GetBytes(pepper);
}
public string HashPhone(string phone)
{
var normalized = (phone ?? string.Empty).Trim();
var hash = HMACSHA256.HashData(_pepper, Encoding.UTF8.GetBytes(normalized));
var hex = Convert.ToHexString(hash).ToLowerInvariant();
return $"{_version}:{hex[.._tokenLength]}";
}
public string MaskPhone(string phone)
{
var p = (phone ?? string.Empty).Trim();
if (p.Length < 8)
{
return p.Length <= 1 ? "***" : $"{p[0]}***{p[^1]}";
}
return $"{p[..4]}***{p[^3..]}";
}
}
Passes — identical body, only the : ISomeFreshUnusedInterfaceName123 base-list clause removed:
namespace Test;
public sealed class PiiHasherFull
{
// ...same fields/ctor/methods as above, no base-list clause...
}
→ returns {functions: 3, classes: 1, ...} correctly.
The interface referenced in the base-list clause (ISomeFreshUnusedInterfaceName123) is never declared anywhere in the call — this isn't about resolving it, just about the base-list syntax co-occurring with the method bodies below.
Isolated variables that do NOT reproduce it alone
Each of these, individually, parses fine:
- A class implementing an undeclared interface, with a simple method body (no range/index syntax) —
functions: 1/2, correct.
- A method using C# 8 range/index syntax (
hex[..4], p[^1]) in a class with no base-list clause — parses fine.
- Tuple return types (
(int Length, string Sha8) Foo(...)) — parses fine.
- Relational patterns (
x is >= 4 and <= 64) — parses fine.
- Index expressions inside string interpolation (
$"{p[0]}***{p[^1]}") — parses fine.
- 4-method class with tuple return + no base-list — parses fine (
functions: 4).
Only the combination of (a) a base-list clause (: SomeInterface) on the class and (b) range/index syntax ([.._x], [^1], [^3..]) somewhere in that class's method bodies reproduces the total-file failure.
Second, separate failure mode found in the same session
Indexing an interface declaration and a class implementing that same interface in the same index_code call (regardless of same-file or cross-file) also zeroes out the result for the entire batch, including unrelated files passed in the same call:
// file A
public interface IMultiProbe
{
string M1(string phone);
}
// file B (or same file)
public sealed class MultiProbeIface : IMultiProbe
{
private readonly string _a;
public MultiProbeIface(string a) { _a = a; }
public string M1(string phone) { return phone; }
}
→ whole batch (including any other unrelated files passed alongside) returns all-zero counts.
Re-indexing the same interface name (IMultiProbe) later in a separate call, without also declaring the interface in that call, parses fine — so this isn't a persistent poisoned-symbol issue, it's specific to interface-decl + implementation being resolved together within one call.
Why this matters in practice
Real-world C# codebases (ours included, an Onion/Clean-Architecture .NET project where nearly every concrete service class implements an interface for DI) hit failure mode #1 constantly once any modern C# 8+ range/index syntax is used anywhere in the class — which is common. Because the failure is silent (no error field, no partial extraction, no logged diagnostic), a caller doing a bulk codebase index has no way to tell "this file has 0 functions" from "the parser gave up on this file" — the returned graph looks complete but is actually missing an unknown fraction of real code.
Suggested remediation
- Surface a parse-error/parse-warning signal per file in the
index_code response (even just {path, error: "..."}, distinct from a legitimately empty file) so silent data loss is at least detectable.
- Root-cause and fix the underlying tree-sitter grammar/query interaction between C# base-list clauses and range/index expressions.
- Consider whether the query used to extract function/class nodes is (incorrectly) scoped such that any node-type it doesn't expect anywhere in the file aborts extraction for the whole file, rather than degrading gracefully per-declaration.
Environment
engramgraph@0.7.0 (npm registry latest as of 2026-07-20 is also 0.7.0, so not fixed by upgrading)
- Reproduced via the
index_code MCP tool (egr mcp server), Windows 11, Node 24
- All repro snippets above are self-contained minimal cases, not excerpts requiring project context
Summary
Via the
index_codeMCP tool (equivalent toegr index's underlying parser), C# (.cs) source is now parsed onengramgraph@0.7.0— a real improvement over what we'd observed before. However, we found a silent whole-file parse failure: certain common, valid C# constructs causeindex_codeto returnfunctions: 0, classes: 0for the entire file, with no error, warning, or partial result — indistinguishable from "this file legitimately has no functions."This is worse than "C# unsupported," because it looks like success. A consumer indexing a codebase has no signal that a file silently produced nothing.
Minimal repro
Confirmed via bisection (isolating one variable at a time) on
engramgraph@0.7.0, called through the MCPindex_codetool.Fails — returns
{functions: 0, classes: 0, ...}for the whole file:Passes — identical body, only the
: ISomeFreshUnusedInterfaceName123base-list clause removed:→ returns
{functions: 3, classes: 1, ...}correctly.The interface referenced in the base-list clause (
ISomeFreshUnusedInterfaceName123) is never declared anywhere in the call — this isn't about resolving it, just about the base-list syntax co-occurring with the method bodies below.Isolated variables that do NOT reproduce it alone
Each of these, individually, parses fine:
functions: 1/2, correct.hex[..4],p[^1]) in a class with no base-list clause — parses fine.(int Length, string Sha8) Foo(...)) — parses fine.x is >= 4 and <= 64) — parses fine.$"{p[0]}***{p[^1]}") — parses fine.functions: 4).Only the combination of (a) a base-list clause (
: SomeInterface) on the class and (b) range/index syntax ([.._x],[^1],[^3..]) somewhere in that class's method bodies reproduces the total-file failure.Second, separate failure mode found in the same session
Indexing an interface declaration and a class implementing that same interface in the same
index_codecall (regardless of same-file or cross-file) also zeroes out the result for the entire batch, including unrelated files passed in the same call:→ whole batch (including any other unrelated files passed alongside) returns all-zero counts.
Re-indexing the same interface name (
IMultiProbe) later in a separate call, without also declaring the interface in that call, parses fine — so this isn't a persistent poisoned-symbol issue, it's specific to interface-decl + implementation being resolved together within one call.Why this matters in practice
Real-world C# codebases (ours included, an Onion/Clean-Architecture .NET project where nearly every concrete service class implements an interface for DI) hit failure mode #1 constantly once any modern C# 8+ range/index syntax is used anywhere in the class — which is common. Because the failure is silent (no error field, no partial extraction, no logged diagnostic), a caller doing a bulk codebase index has no way to tell "this file has 0 functions" from "the parser gave up on this file" — the returned graph looks complete but is actually missing an unknown fraction of real code.
Suggested remediation
index_coderesponse (even just{path, error: "..."}, distinct from a legitimately empty file) so silent data loss is at least detectable.Environment
engramgraph@0.7.0(npm registry latest as of 2026-07-20 is also 0.7.0, so not fixed by upgrading)index_codeMCP tool (egr mcpserver), Windows 11, Node 24