abhigyanpatwari/GitNexus · warning
Could not remove the shadowed branch sub-index; keeping its
Error message
Could not remove the shadowed branch sub-index; keeping its registry summary so `gitnexus clean --branch` can still target it.
What it means
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.
Source
Thrown at gitnexus/src/storage/repo-manager.ts:1169
// un-cleanable disk bloat (#2364 review F4). A resolved force:true rm
// proves absence; on failure, probe the disk and treat only
// provably-absent errno as gone — EACCES/EIO are "not provably absent",
// the same polarity as listRegisteredRepos({ validate: true }).
if (!rmError) {
dirGone = true;
} else {
const probeCode = await fs.access(branchDir).then(
() => null,
(e: unknown) => (e as NodeJS.ErrnoException)?.code ?? 'UNKNOWN',
);
dirGone = probeCode === 'ENOENT' || probeCode === 'ENOTDIR';
}
if (dirGone) {
// Non-recursive by design: only removes the parent when no other pinned
// sub-index remains, so an empty branches/ dir doesn't read as "pinned".
await fs.rmdir(path.join(storagePath, BRANCHES_DIR)).catch(() => {});
} else {
logger.warn(
{ path: branchDir, code: rmError?.code },
'Could not remove the shadowed branch sub-index; keeping its registry summary so `gitnexus clean --branch` can still target it.',
);
}
}
// Re-read AFTER the potentially slow recursive rm, and under the lock: the
// registry is a multi-writer whole-file overwrite, and writing a pre-rm
// snapshot would silently clobber concurrent registerRepo/removeBranchIndex
// writers — the #2106 R9 re-read-before-write discipline registerRepo follows.
await withRegistryLock(async () => {
const entries = await readRegistry();
const idx = isRegistered(entries);
if (idx < 0) return; // unregistered concurrently → still a no-op
const entry = entries[idx];
const remaining = dirGone ? entry.branches?.filter((b) => b.branch !== branch) : entry.branches;
const droppedSummary = (entry.branches?.length ?? 0) !== (remaining?.length ?? 0);
if (entry.branch === branch && !droppedSummary) return; // already coherentView on GitHub (pinned to 0d1aed942f)
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
Defensive patterns
Strategy: retry
Validate before calling
const code = await fs.access(branchDir).then(
() => null,
(e: unknown) => (e as NodeJS.ErrnoException)?.code ?? 'UNKNOWN',
);
if (code !== null && code !== 'ENOENT' && code !== 'ENOTDIR') {
// removal failed for a non-'gone' reason: free locks / fix permissions,
// then retry `gitnexus clean --branch` (its registry summary was kept).
} Type guard
function isGone(code: string | null): boolean {
return code === 'ENOENT' || code === 'ENOTDIR';
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GitNexus could not move the LadybugDB WAL sidecar at ${dbPat
- Refusing to delete ${dbPath}: resolved path ${realPath} is o
- GitNexus: failed to release init lock (${code ?? 'UNKNOWN'})
- Clone failed and partial checkout could not be quarantined:
- Clone target must be a subdirectory of ${CLONE_ROOT}
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/52958c3bec4330b9.
Report an issue: GitHub.