{"record":{"id":"eae64694148e0de3","repo":"abhigyanpatwari/GitNexus","slug":"csharp-namespace-scan-of-reporoot-truncated","errorCode":null,"errorMessage":"[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","messagePattern":"\\[csharp\\] namespace scan of (.+?) truncated \\(dir cap (.+?), depth cap (.+?), an unreadable directory, or an unreadable \\.cs file\\); the #1881 suffix-fallback gate fails open for unmatched usings","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gitnexus/src/core/ingestion/language-config.ts","lineNumber":458,"sourceCode":"      (name) => collectDeclaredNamespaces(path.join(dir, name), declaredNamespaces, rootNamespaces),\n      { concurrency: CSHARP_SCAN_READ_CONCURRENCY },\n    );\n    // A `.cs` that was unreadable (or whose read/scan unexpectedly rejected)\n    // leaves its namespaces uncollected → mark truncated to fail the #1881\n    // gate OPEN rather than wrongly suppress an import. The scan streams each\n    // file, so file size no longer trips truncation. A rejected read arrives\n    // here as `undefined`, which is `!== 'ok'` just like the old\n    // `r.status !== 'fulfilled'` arm.\n    for (const r of csResults) {\n      if (r !== 'ok') truncated = true;\n    }\n  }\n\n  if (truncated) {\n    // Surface the fail-open so an incomplete scan (dir/depth cap, or an\n    // unreadable directory or `.cs` file) silently disabling the #1881 gate\n    // repo-wide is observable (#4) rather than a mystery edge regression.\n    logger.warn(\n      `[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`,\n    );\n  }\n  return { configs, declaredNamespaces, rootNamespaces, truncated };\n}\n\n// Generous soft budget for locating `<RootNamespace>`: a real .csproj declares\n// it in the first PropertyGroup near the top, so this is only reached by a\n// pathological project file with a huge leading ItemGroup and no early\n// RootNamespace. On hit we OMIT the config rather than guess a root (Codex F4).\nconst CSPROJ_ROOT_SCAN_MAX_BYTES = 4 * 1024 * 1024;\n// Overlap kept across stream chunks so a `<RootNamespace>` tag straddling a\n// chunk boundary is still matched (the tag + a short namespace value fit well\n// within this window).\nconst CSPROJ_TAG_OVERLAP = 512;\n\n/**\n * Stream a `.csproj` just far enough to find `<RootNamespace>`, in constant","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/core/ingestion/language-config.ts#L440-L476","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"# before — committed bin/obj push scan over 20000 dirs\nbin/\nobj/\n (committed)\n\n# after\necho 'bin/\nobj/' >> .gitignore\ngit rm -r --cached bin obj","handlingStrategy":"validation","validationCode":"import { readdirSync, accessSync, constants } from 'node:fs';\n\nfunction auditCsharpScan(root: string): { dirs: number; maxDepth: number; unreadable: string[] } {\n  let dirs = 0, maxDepth = 0;\n  const unreadable: string[] = [];\n  const walk = (dir: string, depth: number) => {\n    dirs++; maxDepth = Math.max(maxDepth, depth);\n    if (dirs > 20_000 || depth > 24) return;\n    let entries;\n    try { entries = readdirSync(dir, { withFileTypes: true }); }\n    catch { unreadable.push(dir); return; }\n    for (const e of entries) {\n      if (e.isDirectory()) walk(`${dir}/${e.name}`, depth + 1);\n      else if (e.name.endsWith('.cs')) {\n        try { accessSync(`${dir}/${e.name}`, constants.R_OK); }\n        catch { unreadable.push(`${dir}/${e.name}`); }\n      }\n    }\n  };\n  walk(root, 0);\n  return { dirs, maxDepth, unreadable };\n}\nconst a = auditCsharpScan(repoRoot);\nif (a.dirs >= 20_000 || a.maxDepth >= 24 || a.unreadable.length > 0) {\n  console.warn('C# namespace scan would truncate', a);\n}","typeGuard":"function csharpScanWillComplete(a: { dirs: number; maxDepth: number; unreadable: string[] }): boolean {\n  return a.dirs < 20_000 && a.maxDepth < 24 && a.unreadable.length === 0;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["csharp","namespace-scan","scan-limit","fail-open"],"backgroundTag":"scan-limit-exceeded","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}