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 winsView on GitHub (pinned to aac7515d2a)
Solutions
- Identify the named holder (pid on hostname) with ps, let it finish or kill it if wedged, then retry analyze
- Serialize analyze runs at the orchestration layer (CI mutex/queue, single scheduler) so only one runs per repo
- Raise the ceiling for very large repos: GITNEXUS_INDEX_LOCK_TIMEOUT_MS=3600000
- 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
- Serialize analyze runs per repo with an orchestration-level mutex or CI queue
- Set GITNEXUS_INDEX_LOCK_TIMEOUT_MS above your worst-case reindex duration on big repos
- Check the holder pid with ps before killing anything — a live long reindex is healthy, not stuck
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out after ${waitedMs}ms waiting for another gitnexus a
- GitNexus: unable to acquire init lock after ${INIT_LOCK_MAX_
- Worker ${workerIndex} replacement failed to come online; dro
- GitNexus: unable to inspect init lock (${code ?? 'UNKNOWN'})
- Circuit '${key}' is open; retry in ${Math.ceil(retryAfterMs
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/a572573e28cecafb.
Report an issue: GitHub.