abhigyanpatwari/GitNexus · error · LbugWipeError

Failed to remove the LadybugDB index files — still present…

Error message

Failed to remove the LadybugDB index files — still present after 5 attempts:
${survivors.map((p) => `  - ${p}`).join('\n')}
The blocking handle may be another process, a lingering handle from this process's just-closed database, or an antivirus scan — an immediate re-run often succeeds. If it persists, stop any GitNexus MCP or serve process using this repository, add an antivirus exclusion for the GitNexus storage directory, then re-run the analyze.

What it means

LbugWipeError from wipeLbugDbFiles (gitnexus/src/core/lbug/lbug-adapter.ts:2383). A full-rebuild wipe rm-probes each member of the 4-file family (<db>, .wal, .shadow, .lock) and counts it gone only on ENOENT. After HANDLE_RELEASE_PROBE_ATTEMPTS (5) retries, data-bearing survivors — everything except the contentless .lock, which is tolerated with a warning — fail the rebuild, because reopening would resurrect rows the run believes it wiped.

Solutions

  1. Immediately re-run the analyze — transient handle-release lag often clears on its own
  2. Stop every GitNexus MCP or serve process using this repository, then re-run
  3. Add an antivirus exclusion for the GitNexus storage directory
  4. On POSIX, confirm no holder remains: lsof +D <storage-dir>, kill the listed PIDs, re-run
Defensive patterns

Strategy: retry

Validate before calling

// before a forced rebuild, confirm nothing holds the index family open (POSIX)
import { execFile } from 'node:child_process';
const holders = await new Promise<string>((res) =>
  execFile('lsof', ['-t', dbPath, `${dbPath}.wal`, `${dbPath}.shadow`],
    (_e, stdout) => res(stdout.toString().trim())),
);
if (holders) throw new Error(`index still held by PIDs: ${holders}`);

Type guard

import { LbugWipeError } from '../core/lbug/lbug-adapter.js';
const isLbugWipeError = (e: unknown): e is LbugWipeError => e instanceof LbugWipeError;

Try / catch

try {
  await wipeLbugDbFiles(dbPath);
} catch (e) {
  if (isLbugWipeError(e)) {
    // transient handle lag — stop holders, then retry the wipe once before failing
    await stopGitNexusServeProcesses();
    await retryOnce(() => wipeLbugDbFiles(dbPath));
  }
  throw e;
}

Prevention

When it happens

Trigger: gitnexus analyze --force (or the #2409 escalation wipe) while another GitNexus MCP/serve process still holds the database files open; Windows delete-pending handle-release lag; an antivirus scan holding handles on the storage directory.

Common situations: Forgetting to stop `gitnexus serve` or an MCP session before a forced rebuild on Windows; on-access AV scanners touching the storage dir mid-wipe; a just-closed database in the same process whose handles have not been released yet.

Related errors


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

Appendix: source

Thrown at gitnexus/src/core/lbug/lbug-adapter.ts:2495

      if (!gone) survivors.push(f);
    }
    if (survivors.length === 0) return;
    if (attempt < HANDLE_RELEASE_PROBE_ATTEMPTS) {
      await sleep(HANDLE_RELEASE_PROBE_DELAY_MS * attempt);
    }
  }

  // Class split (FIX 2): the contentless `.lock` never fails the wipe.
  const dataSurvivors = survivors.filter((f) => f !== lockPath);
  if (survivors.includes(lockPath)) {
    logger.warn(
      `GitNexus: ${lockPath} is still present after the wipe retries — continuing: the ` +
        'lock file is contentless and initLbug recreates it; a genuinely held lock will ' +
        "surface as the reopen's own lock-busy error.",
    );
  }
  if (dataSurvivors.length > 0) {
    throw new LbugWipeError(dataSurvivors);
  }
};

export const isLbugReady = (): boolean => conn !== null && db !== null;

/**
 * Multi-label alternation over exactly the labels that can own embedding
 * rows: EMBEDDABLE_LABELS plus File, which embedding-pipeline.ts embeds as
 * the zero-symbol fallback for text-only repositories (#2454). Reserved
 * keywords are backtick-escaped via {@link escapeTableName}. Probed on
 * @ladybugdb/core 0.18.0 (this shipping review, FIX 4): the full multi-label
 * alternation parses, executes, and deletes exactly the joined rows —
 * replacing the unlabeled `MATCH (n)` that scanned EVERY node table per
 * chunk (BasicBlock-dominated under `--pdg`) when only embeddable labels
 * can match an embedding row. Including File is free for code repositories:
 * they never hold File embedding rows, so the extra label joins nothing.
 */
const embeddableLabelMatch = (): string =>

View on GitHub (pinned to ac9a4e9abd)