abhigyanpatwari/GitNexus · error · IndexLockTimeoutError

Timed out after ${waitedMs}ms waiting for another gitnexus a

Error message

Timed out after ${waitedMs}ms waiting for another gitnexus analyze (pid ${holder.pid} on ${holder.hostname}, invocation ${holder.invocationId}) to release the index lock.

What it means

gitnexus analyze serializes index writes with a per-index lock. The file backend reads the holder record (pid, hostname, invocationId) and waits, announcing the other run; if the live holder does not release within the wait ceiling (default 600,000 ms from DEFAULT_TIMEOUT_MS; override via AcquireOptions.timeoutMs or GITNEXUS_INDEX_LOCK_TIMEOUT_MS, ≤0 for unbounded), acquisition fails with IndexLockTimeoutError naming the holder.

Source

Thrown at gitnexus/src/storage/index-lock.ts:486

      malformedSince = null;
      if (isStale(holder)) {
        opts.log?.(
          `Reclaiming stale index lock from dead analyze (pid ${holder.pid}, ` +
            `invocation ${holder.invocationId}).`,
        );
        stealLock(lockPath, me, holder); // reclaim ONLY this dead record; live locks are never stolen
        continue;
      }
      // Live holder → wait.
      if (!announcedWait) {
        announcedWait = true;
        opts.onWaitStart?.(holder);
        opts.log?.(
          `Another gitnexus analyze (pid ${holder.pid} on ${holder.hostname}) is ` +
            `refreshing this index — waiting for it to finish.`,
        );
      }
      if (waited >= timeoutMs) throw new IndexLockTimeoutError(holder, waited);
      if (Date.now() - lastDiagnosticAt >= DIAGNOSTIC_INTERVAL_MS) {
        lastDiagnosticAt = Date.now();
        if (waited >= DIAGNOSTIC_INTERVAL_MS) {
          opts.log?.(
            `Still waiting for analyze pid ${holder.pid} (${Math.round(waited / 1000)}s elapsed).`,
          );
        }
      }
      await sleep(jitteredDelay(pollMs, timeoutMs, waited));
      continue;
    }

    // holder === null: the lock file is either gone (vanished between the failed
    // create and our read) or present-but-unreadable (a crash between the
    // O_EXCL create and the record write, or a partial write). NEVER hot-loop
    // here — both branches are bounded by sleep + timeout.
    if (!existsSync(lockPath)) {
      malformedSince = null; // genuinely vanished → the next create likely wins

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Identify the named holder (pid on hostname) with ps, let it finish or kill it if wedged, then retry analyze
  2. Serialize analyze runs at the orchestration layer (CI mutex/queue, single scheduler) so only one runs per repo
  3. Raise the ceiling for very large repos: GITNEXUS_INDEX_LOCK_TIMEOUT_MS=3600000
  4. Confirm both runs targeting the same index is intended — if not, point one at a different repo/storage path

Example fix

# before — times out at the 10-minute default on a huge repo
 gitnexus analyze .

# after
GITNEXUS_INDEX_LOCK_TIMEOUT_MS=3600000 gitnexus analyze .
Defensive patterns

Strategy: retry

Validate before calling

import { execSync } from 'node:child_process';
function otherAnalyzeRunning(): boolean {
  try {
    const out = execSync('pgrep -af "gitnexus analyze" || true', { encoding: 'utf8' });
    return out
      .split('\n')
      .some((l) => l.trim() && !l.startsWith(String(process.pid)));
  } catch {
    return false;
  }
}
if (otherAnalyzeRunning()) await waitForOtherAnalyze();

Try / catch

try {
  await runAnalyze();
} catch (e) {
  if (e?.name === 'IndexLockTimeoutError' && e.holder?.pid) {
    console.warn(`holder pid ${e.holder.pid} on ${e.holder.hostname} — retrying once after it exits`);
    await sleep(60_000);
    return runAnalyze(); // one bounded retry, not a loop
  }
  throw e;
}

Prevention

When it happens

Trigger: Two `gitnexus analyze` runs on the same repo overlap — e.g. a scheduled CI refresh still running while a developer or IDE/MCP-triggered analyze starts — and the holder's run exceeds the timeout (10 minutes by default).

Common situations: CI cron overlapping manual runs; very large repos whose reindex legitimately runs past 10 minutes; shared/mounted storage where another host's analyze holds the lock.

Understand the failure class

Related errors


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