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 (holder identity unknown) to release the index lock. What it means
In the file-backend wait loop the lock file repeatedly vanished between this process's failed O_EXCL create and its read — other processes kept winning the lock, finishing, and unlinking it. This process starved until timeoutMs without ever reading a holder record, so the timeout reports an unknown holder (unknownHolder()). The loop is sleep-bounded by design; the failure means contention churn, not corruption.
Source
Thrown at gitnexus/src/storage/index-lock.ts:505
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
if (waited >= timeoutMs) throw new IndexLockTimeoutError(unknownHolder(), waited, false);
await sleep(jitteredDelay(pollMs, timeoutMs, waited));
continue;
}
// Malformed orphan present. Reclaim only after a grace, so a live owner's
// microsecond create→write window is never mistaken for a crash.
if (malformedSince === null) malformedSince = Date.now();
if (Date.now() - malformedSince >= malformedGraceMs(pollMs)) {
opts.log?.('Reclaiming a malformed/partial index lock file (no readable owner record).');
stealLock(lockPath, me, null); // reclaim ONLY while still unreadable; a live lock written since is left
malformedSince = null;
continue;
}
if (waited >= timeoutMs) throw new IndexLockTimeoutError(unknownHolder(), waited, false);
await sleep(jitteredDelay(pollMs, timeoutMs, waited));
}
};
/** Signals that the OS socket backend can't be used here (e.g. abstractView on GitHub (pinned to aac7515d2a)
Solutions
- Serialize analyze across all workers: shared CI lock, single scheduler, or staggered cron with jitter
- Retry later with backoff — the lock itself is healthy; only contention is the problem
- Raise GITNEXUS_INDEX_LOCK_TIMEOUT_MS so a queued run eventually gets its turn
- Index once into shared storage, or give each worker its own repo copy
Defensive patterns
Strategy: retry
Validate before calling
// orchestration-level: one analyze at a time per repo
await withMutex(`gitnexus:${repoKey}`, () => runAnalyze()); Try / catch
try {
await runAnalyze();
} catch (e) {
if (e?.name === 'IndexLockTimeoutError' && e.holder == null) {
await sleep(5 * 60_000); // contention churn — back off and retry later
return runAnalyze();
}
throw e;
} Prevention
- Never fan out a CI matrix where every shard runs analyze on the same checkout
- Stagger scheduled analyzes with jitter
- Index once into shared storage instead of N concurrent per-worker indexes
When it happens
Trigger: Many concurrent analyze processes racing on the same repo (CI matrix fan-out, immediate retry storms) such that this instance never wins the create race within the timeout window.
Common situations: A CI matrix where every shard runs analyze against the same checkout; schedulers retrying failed analyzes immediately; cron and webhooks firing analyze simultaneously.
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/15d36d9b5dd15214.
Report an issue: GitHub.