{"record":{"id":"52958c3bec4330b9","repo":"abhigyanpatwari/GitNexus","slug":"could-not-remove-the-shadowed-branch-sub-index-ke","errorCode":null,"errorMessage":"Could not remove the shadowed branch sub-index; keeping its registry summary so `gitnexus clean --branch` can still target it.","messagePattern":"Could not remove the shadowed branch sub-index; keeping its registry summary so `gitnexus clean --branch` can still target it\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gitnexus/src/storage/repo-manager.ts","lineNumber":1169,"sourceCode":"    // un-cleanable disk bloat (#2364 review F4). A resolved force:true rm\n    // proves absence; on failure, probe the disk and treat only\n    // provably-absent errno as gone — EACCES/EIO are \"not provably absent\",\n    // the same polarity as listRegisteredRepos({ validate: true }).\n    if (!rmError) {\n      dirGone = true;\n    } else {\n      const probeCode = await fs.access(branchDir).then(\n        () => null,\n        (e: unknown) => (e as NodeJS.ErrnoException)?.code ?? 'UNKNOWN',\n      );\n      dirGone = probeCode === 'ENOENT' || probeCode === 'ENOTDIR';\n    }\n    if (dirGone) {\n      // Non-recursive by design: only removes the parent when no other pinned\n      // sub-index remains, so an empty branches/ dir doesn't read as \"pinned\".\n      await fs.rmdir(path.join(storagePath, BRANCHES_DIR)).catch(() => {});\n    } else {\n      logger.warn(\n        { path: branchDir, code: rmError?.code },\n        'Could not remove the shadowed branch sub-index; keeping its registry summary so `gitnexus clean --branch` can still target it.',\n      );\n    }\n  }\n\n  // Re-read AFTER the potentially slow recursive rm, and under the lock: the\n  // registry is a multi-writer whole-file overwrite, and writing a pre-rm\n  // snapshot would silently clobber concurrent registerRepo/removeBranchIndex\n  // writers — the #2106 R9 re-read-before-write discipline registerRepo follows.\n  await withRegistryLock(async () => {\n    const entries = await readRegistry();\n    const idx = isRegistered(entries);\n    if (idx < 0) return; // unregistered concurrently → still a no-op\n    const entry = entries[idx];\n    const remaining = dirGone ? entry.branches?.filter((b) => b.branch !== branch) : entry.branches;\n    const droppedSummary = (entry.branches?.length ?? 0) !== (remaining?.length ?? 0);\n    if (entry.branch === branch && !droppedSummary) return; // already coherent","sourceCodeStart":1151,"sourceCodeEnd":1187,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/storage/repo-manager.ts#L1151-L1187","documentation":"When removing a branch sub-index, if the recursive removal errored and a follow-up probe shows the directory still exists (not ENOENT/ENOTDIR), GitNexus keeps the branch's registry summary and warns — deliberately, so gitnexus clean --branch can still target the leftover sub-index later. The empty parent branches/ dir is only rmdir'd when no sub-index remains.","triggerScenarios":"Branch sub-index deletion where fs.rm fails and the directory provably survives: EBUSY/EPERM from files held open (Windows file locking, a still-running analyze), EACCES from permission mismatch, or NFS removal semantics. The warning carries the path and the rm error code.","commonSituations":"Antivirus or search indexer holding files open on Windows; storage tree owned by another user; deletion attempted while an analyze on that branch is live.","solutions":["Close whatever holds the branch dir open (or wait for AV/indexer to release it), then re-run the removal or gitnexus clean --branch — the summary was kept exactly so it can still target it","Fix ownership/permissions on the storage tree and retry","As a last resort, remove the branch dir manually and then run the branch clean"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const code = await fs.access(branchDir).then(\n  () => null,\n  (e: unknown) => (e as NodeJS.ErrnoException)?.code ?? 'UNKNOWN',\n);\nif (code !== null && code !== 'ENOENT' && code !== 'ENOTDIR') {\n  // removal failed for a non-'gone' reason: free locks / fix permissions,\n  // then retry `gitnexus clean --branch` (its registry summary was kept).\n}","typeGuard":"function isGone(code: string | null): boolean {\n  return code === 'ENOENT' || code === 'ENOTDIR';\n}","tryCatchPattern":null,"preventionTips":["Do not delete a branch index while an analyze is running on it","On Windows, exclude the storage tree from real-time AV scanning","Fix storage ownership before bulk cleanups"],"tags":["storage","branch-index","filesystem","cleanup","windows"],"backgroundTag":"file-removal-failed","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"}