abhigyanpatwari/GitNexus · error

LadybugDB unavailable for

Error message

LadybugDB unavailable for ${repoId}. Another process may be rebuilding the index. Retry later. (${lastError?.message || 'unknown error'})

What it means

doInitLbug (gitnexus/src/core/lbug/pool-adapter.ts:794): after LOCK_RETRY_ATTEMPTS lock-busy retries — or an open failure that is neither WAL corruption, sidecar-classified, nor lock-busy — no shared Database could be opened. The trailing parenthesized message is the last underlying error; the typical cause is a concurrent writer holding the database lock.

Solutions

  1. Retry once the concurrent analyze finishes — the message says exactly that
  2. Stagger schedules so serve/MCP startup does not race analyze on the same repo
  3. Read the trailing lastError: if it is not lock-shaped, fix that underlying issue (path, permissions)
  4. Never write from MCP/serve — keep writers to analyze so the read-only pool rarely contends
Defensive patterns

Strategy: retry

Validate before calling

// before opening, check whether another writer holds the index (lock file present + alive)
import { readFile } from 'node:fs/promises';
const lockPid = await readFile(`${dbPath}.lock`, 'utf8').then(
  (s) => s.trim(),
  () => '',
);
if (lockPid && (await isProcessAlive(Number(lockPid)))) {
  throw new Error('analyze in progress — defer pool init');
}

Type guard

const isLbugUnavailable = (e: unknown): boolean =>
  e instanceof Error && e.message.startsWith('LadybugDB unavailable for');

Try / catch

let lastErr: unknown;
for (let attempt = 0; attempt < 5; attempt++) {
  try {
    await initLbug(repoId, dbPath);
    break;
  } catch (e) {
    lastErr = e;
    if (!isLbugUnavailable(e)) throw e;
    await sleep(10_000 * (attempt + 1)); // back off while the rebuild finishes
  }
}
if (lastErr) throw lastErr;

Prevention

When it happens

Trigger: initLbug while `gitnexus analyze` (or another serve/MCP instance in write mode) holds the database lock longer than the retry budget; or any open error with no dedicated recovery path (the lastError text says which).

Common situations: Starting an MCP session or serve during a long incremental analyze; several CI jobs opening the same mounted storage simultaneously.

Related errors


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

Appendix: source

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

        if (
          lastError.message.startsWith('LadybugDB checkpoint sidecar is missing') ||
          lastError.message.startsWith('LadybugDB checkpoint sidecar is present but unreachable') ||
          lastError.message.startsWith('GitNexus could not move the LadybugDB WAL sidecar') ||
          isMissingShadowSidecarError(lastError)
        ) {
          throw lastError;
        }

        const isLockError =
          lastError.message.includes('Could not set lock') ||
          /\block(\b|ed|ing)/i.test(lastError.message);
        if (!isLockError || attempt === LOCK_RETRY_ATTEMPTS) break;
        await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_DELAY_MS * attempt));
      }
    }

    if (!shared) {
      throw new Error(
        `LadybugDB unavailable for ${repoId}. Another process may be rebuilding the index. ` +
          `Retry later. (${lastError?.message || 'unknown error'})`,
      );
    }
  }

  shared.refCount++;
  const db = shared.db;

  // Pre-create the full pool upfront so createConnection() (which silences
  // stdout) is never called lazily during active query execution.
  // Mark preWarmActive so the watchdog timer doesn't interfere.
  preWarmActive = true;
  const available: lbug.Connection[] = [];
  try {
    for (let i = 0; i < MAX_CONNS_PER_REPO; i++) {
      available.push(createConnection(db));
    }

View on GitHub (pinned to 52924ef12c)