abhigyanpatwari/GitNexus · warning
[csharp] namespace scan of ${repoRoot} truncated (dir cap ${
Error message
[csharp] namespace scan of ${repoRoot} truncated (dir cap ${CSHARP_SCAN_MAX_DIRS}, depth cap ${CSHARP_SCAN_MAX_DEPTH}, an unreadable directory, or an unreadable .cs file); the #1881 suffix-fallback gate fails open for unmatched usings What it means
The C# config scan builds the set of declared namespaces by walking repoRoot (caps: CSHARP_SCAN_MAX_DIRS = 20,000 directories, CSHARP_SCAN_MAX_DEPTH = 24) and reading .cs files; the result is flagged truncated when any cap hits or a directory/.cs read is rejected. On truncation the #1881 suffix-fallback gate fails open repo-wide: unmatched usings fall back to suffix matching instead of being gated by declared namespaces.
Source
Thrown at gitnexus/src/core/ingestion/language-config.ts:458
(name) => collectDeclaredNamespaces(path.join(dir, name), declaredNamespaces, rootNamespaces),
{ concurrency: CSHARP_SCAN_READ_CONCURRENCY },
);
// A `.cs` that was unreadable (or whose read/scan unexpectedly rejected)
// leaves its namespaces uncollected → mark truncated to fail the #1881
// gate OPEN rather than wrongly suppress an import. The scan streams each
// file, so file size no longer trips truncation. A rejected read arrives
// here as `undefined`, which is `!== 'ok'` just like the old
// `r.status !== 'fulfilled'` arm.
for (const r of csResults) {
if (r !== 'ok') truncated = true;
}
}
if (truncated) {
// Surface the fail-open so an incomplete scan (dir/depth cap, or an
// unreadable directory or `.cs` file) silently disabling the #1881 gate
// repo-wide is observable (#4) rather than a mystery edge regression.
logger.warn(
`[csharp] namespace scan of ${repoRoot} truncated (dir cap ${CSHARP_SCAN_MAX_DIRS}, depth cap ${CSHARP_SCAN_MAX_DEPTH}, an unreadable directory, or an unreadable .cs file); the #1881 suffix-fallback gate fails open for unmatched usings`,
);
}
return { configs, declaredNamespaces, rootNamespaces, truncated };
}
// Generous soft budget for locating `<RootNamespace>`: a real .csproj declares
// it in the first PropertyGroup near the top, so this is only reached by a
// pathological project file with a huge leading ItemGroup and no early
// RootNamespace. On hit we OMIT the config rather than guess a root (Codex F4).
const CSPROJ_ROOT_SCAN_MAX_BYTES = 4 * 1024 * 1024;
// Overlap kept across stream chunks so a `<RootNamespace>` tag straddling a
// chunk boundary is still matched (the tag + a short namespace value fit well
// within this window).
const CSPROJ_TAG_OVERLAP = 512;
/**
* Stream a `.csproj` just far enough to find `<RootNamespace>`, in constantView on GitHub (pinned to 0d1aed942f)
Solutions
- Fix unreadable directories/.cs files (permissions) so every read succeeds
- Exclude bin/obj/vendor trees via ignore rules to get under the dir and depth caps
- Restructure project folders nested deeper than 24 levels
- If truncation persists, treat unmatched-using resolution as fail-open when reviewing results
Example fix
# before — committed bin/obj push scan over 20000 dirs bin/ obj/ (committed) # after echo 'bin/ obj/' >> .gitignore git rm -r --cached bin obj
Defensive patterns
Strategy: validation
Validate before calling
import { readdirSync, accessSync, constants } from 'node:fs';
function auditCsharpScan(root: string): { dirs: number; maxDepth: number; unreadable: string[] } {
let dirs = 0, maxDepth = 0;
const unreadable: string[] = [];
const walk = (dir: string, depth: number) => {
dirs++; maxDepth = Math.max(maxDepth, depth);
if (dirs > 20_000 || depth > 24) return;
let entries;
try { entries = readdirSync(dir, { withFileTypes: true }); }
catch { unreadable.push(dir); return; }
for (const e of entries) {
if (e.isDirectory()) walk(`${dir}/${e.name}`, depth + 1);
else if (e.name.endsWith('.cs')) {
try { accessSync(`${dir}/${e.name}`, constants.R_OK); }
catch { unreadable.push(`${dir}/${e.name}`); }
}
}
};
walk(root, 0);
return { dirs, maxDepth, unreadable };
}
const a = auditCsharpScan(repoRoot);
if (a.dirs >= 20_000 || a.maxDepth >= 24 || a.unreadable.length > 0) {
console.warn('C# namespace scan would truncate', a);
} Type guard
function csharpScanWillComplete(a: { dirs: number; maxDepth: number; unreadable: string[] }): boolean {
return a.dirs < 20_000 && a.maxDepth < 24 && a.unreadable.length === 0;
} Prevention
- Keep bin/obj/vendor trees out of the repository or under ignore rules
- Avoid project nesting deeper than 24 levels
- Fix permissions on checked-in directories so no .cs read is rejected — one unreadable file fails the gate open repo-wide
When it happens
Trigger: Directory count over 20,000, project nesting deeper than 24 levels, or an unreadable directory/.cs file (permissions) during the namespace scan — any of these sets truncated=true.
Common situations: Solution folders with deep obj/bin nesting; permission-restricted checked-in folders; monorepos above the dir cap. Observable symptom: suspiciously permissive using-directive matches (fail-open suffix fallback).
Related errors
- [cfg] C# buildFunctionCfg skipped a function in ${filePath}:
- [node] package.json scan of ${repoRoot} hit the ${SCAN_MAX_D
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/eae64694148e0de3.
Report an issue: GitHub.