{"record":{"id":"aa0f3fce7d7eed0e","repo":"abhigyanpatwari/GitNexus","slug":"ladybugdb-unavailable-for-repoid-another-process-may-be","errorCode":null,"errorMessage":"LadybugDB unavailable for ${repoId}. Another process may be rebuilding the index. Retry later. (${err?.message || 'unknown error'})","messagePattern":"LadybugDB unavailable for (.+?)\\. Another process may be rebuilding the index\\. Retry later\\. \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gitnexus/src/core/lbug/pool-adapter.ts","lineNumber":760,"sourceCode":" * *sleeps* run outside the mutex so one analyze-locked repo does not block\n * every other pool init for LOCK_RETRY_DELAY_MS * attempt.\n *\n * Returns `true` when this call (re)opened a fresh handle onto the current\n * on-disk file, `false` when it reused/served the existing handle (unchanged,\n * or changed-but-a-query-is-in-flight). Callers that gate their own freshness\n * bookkeeping on \"did the pool actually roll over\" (LocalBackend) use the\n * return value; callers that only need the pool ready can ignore it.\n */\nexport const initLbug = async (repoId: string, dbPath: string): Promise<boolean> => {\n  let lastError: Error | undefined;\n  for (let attempt = 1; attempt <= LOCK_RETRY_ATTEMPTS; attempt++) {\n    const result = await withPoolLock(() => initLbugInner(repoId, dbPath));\n    if (result.status === 'done') return result.reopened;\n    lastError = result.error;\n    if (attempt === LOCK_RETRY_ATTEMPTS) break;\n    await sleep(LOCK_RETRY_DELAY_MS * attempt);\n  }\n  throw ladybugUnavailableError(repoId, lastError);\n};\n\nconst initLbugInner = async (repoId: string, dbPath: string): Promise<InitLbugAttempt> => {\n  const existing = pool.get(repoId);\n  if (existing) {\n    existing.lastUsed = Date.now();\n    // Detect an index that `analyze` rebuilt or mutated under this live read\n    // pool. Without this, the pool keeps serving the old (POSIX:\n    // unlinked-but-open) inode until LRU/idle eviction — a stale-read window\n    // of up to IDLE_TIMEOUT_MS after analyze finishes.\n    const current = await statDbIdentity(dbPath);\n    if (!dbIdentityChanged(existing.dbIdentity, current)) {\n      return { status: 'done', reopened: false }; // unchanged → reuse\n    }\n    // A query is in flight on this entry; closing its connection (and the\n    // shared Database at refCount 0) mid-use is a native use-after-free. Serve\n    // the current handle for this dispatch — the next initLbug that finds the\n    // entry idle (checkedOut === 0) reopens, since the identity stays divergent","sourceCodeStart":742,"sourceCodeEnd":778,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/core/lbug/pool-adapter.ts#L742-L778","documentation":"initLbug opens (or reuses) the LadybugDB database handle for a repo and retries the native open up to LOCK_RETRY_ATTEMPTS times when it fails with a lock error. If every retry still reports a lock conflict (e.g. a concurrent `gitnexus analyze` holds the file), it gives up and throws this 'LadybugDB unavailable' error. It signals transient lock contention, not corruption; the caller is expected to retry later.","triggerScenarios":"Calling initLbug (directly or through the MCP/local query backend) while another process holds the database file lock — typically a concurrent `gitnexus analyze` writing the index — such that all retry attempts with linear backoff (LOCK_RETRY_DELAY_MS * attempt) expire while the writer still holds the lock.","commonSituations":"Running an MCP server session while `gitnexus analyze --index-only` rebuilds the index in another terminal; CI jobs that index and query in parallel; a crashed analyze process that left a stale lock; multiple MCP server instances contending for the same repo at startup.","solutions":["Wait for the concurrent `gitnexus analyze` to finish, then retry the query or restart the session — this error is designed to be transient.","Check for a stale/abandoned analyze process (e.g. `ps aux | grep gitnexus`) and terminate it if it crashed while holding the lock.","Re-run `gitnexus analyze` once the repo is quiet to rebuild a clean index and release locks.","If contention is chronic, serialize access: run queries through one server and avoid triggering analyze while a backend session is active."],"exampleFix":"// before\nawait initLbug(repoId, dbPath); // throws under concurrent analyze\n// after\ntry {\n  await initLbug(repoId, dbPath);\n} catch (err) {\n  if (String(err).includes('LadybugDB unavailable')) {\n    await new Promise((r) => setTimeout(r, 2000)); // let the concurrent analyze release the lock\n    await initLbug(repoId, dbPath);\n  } else throw err;\n}","handlingStrategy":"retry","validationCode":"import fs from 'node:fs';\n// before querying, verify the index exists and no analyze appears to be running\nif (!fs.existsSync(dbPath)) throw new Error('Run: gitnexus analyze');\n// optionally: detect a live gitnexus analyze process before proceeding","typeGuard":"const isLadybugUnavailable = (err: unknown): err is Error =>\n  err instanceof Error && err.message.startsWith('LadybugDB unavailable for ');","tryCatchPattern":"try {\n  await initLbug(repoId, dbPath);\n} catch (err) {\n  if (isLadybugUnavailable(err)) {\n    // transient: wait for the concurrent analyze, then retry with backoff\n    await new Promise((r) => setTimeout(r, 5000));\n    await initLbug(repoId, dbPath);\n  } else throw err;\n}","preventionTips":["Avoid running `gitnexus analyze` and MCP query sessions concurrently on the same repo; serialize them.","Schedule indexing (analyze) outside query-heavy windows.","Use one long-lived MCP server instead of spawning multiple processes against the same index.","Use exponential backoff with a cap in retry loops so long analyze runs are tolerated.","Alert on repeated occurrences — it usually means a crashed analyze left a stale lock."],"tags":["database","lock-contention","retry","indexing"],"backgroundTag":"database-locked","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-09-15T23:29:44.066Z","contentChangedAt":"2026-09-15T23:29:44.066Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}