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 coherent

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. 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
  2. Fix ownership/permissions on the storage tree and retry
  3. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20). Data as JSON: /api/errors/52958c3bec4330b9. Report an issue: GitHub.