{"record":{"id":"15d36d9b5dd15214","repo":"abhigyanpatwari/GitNexus","slug":"timed-out-after-waitedms-ms-waiting-for-another-15d36d","errorCode":null,"errorMessage":"Timed out after ${waitedMs}ms waiting for another gitnexus analyze (holder identity unknown) to release the index lock.","messagePattern":"Timed out after (.+?)ms waiting for another gitnexus analyze \\(holder identity unknown\\) to release the index lock\\.","errorType":"exception","errorClass":"IndexLockTimeoutError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/storage/index-lock.ts","lineNumber":505,"sourceCode":"      if (Date.now() - lastDiagnosticAt >= DIAGNOSTIC_INTERVAL_MS) {\n        lastDiagnosticAt = Date.now();\n        if (waited >= DIAGNOSTIC_INTERVAL_MS) {\n          opts.log?.(\n            `Still waiting for analyze pid ${holder.pid} (${Math.round(waited / 1000)}s elapsed).`,\n          );\n        }\n      }\n      await sleep(jitteredDelay(pollMs, timeoutMs, waited));\n      continue;\n    }\n\n    // holder === null: the lock file is either gone (vanished between the failed\n    // create and our read) or present-but-unreadable (a crash between the\n    // O_EXCL create and the record write, or a partial write). NEVER hot-loop\n    // here — both branches are bounded by sleep + timeout.\n    if (!existsSync(lockPath)) {\n      malformedSince = null; // genuinely vanished → the next create likely wins\n      if (waited >= timeoutMs) throw new IndexLockTimeoutError(unknownHolder(), waited, false);\n      await sleep(jitteredDelay(pollMs, timeoutMs, waited));\n      continue;\n    }\n    // Malformed orphan present. Reclaim only after a grace, so a live owner's\n    // microsecond create→write window is never mistaken for a crash.\n    if (malformedSince === null) malformedSince = Date.now();\n    if (Date.now() - malformedSince >= malformedGraceMs(pollMs)) {\n      opts.log?.('Reclaiming a malformed/partial index lock file (no readable owner record).');\n      stealLock(lockPath, me, null); // reclaim ONLY while still unreadable; a live lock written since is left\n      malformedSince = null;\n      continue;\n    }\n    if (waited >= timeoutMs) throw new IndexLockTimeoutError(unknownHolder(), waited, false);\n    await sleep(jitteredDelay(pollMs, timeoutMs, waited));\n  }\n};\n\n/** Signals that the OS socket backend can't be used here (e.g. abstract","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/storage/index-lock.ts#L487-L523","documentation":"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.","triggerScenarios":"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.","commonSituations":"A CI matrix where every shard runs analyze against the same checkout; schedulers retrying failed analyzes immediately; cron and webhooks firing analyze simultaneously.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// orchestration-level: one analyze at a time per repo\nawait withMutex(`gitnexus:${repoKey}`, () => runAnalyze());","typeGuard":null,"tryCatchPattern":"try {\n  await runAnalyze();\n} catch (e) {\n  if (e?.name === 'IndexLockTimeoutError' && e.holder == null) {\n    await sleep(5 * 60_000); // contention churn — back off and retry later\n    return runAnalyze();\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["concurrency","file-lock","starvation","timeout"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}