{"record":{"id":"a572573e28cecafb","repo":"abhigyanpatwari/GitNexus","slug":"timed-out-after-waitedms-ms-waiting-for-another","errorCode":null,"errorMessage":"Timed out after ${waitedMs}ms waiting for another gitnexus analyze (pid ${holder.pid} on ${holder.hostname}, invocation ${holder.invocationId}) to release the index lock.","messagePattern":"Timed out after (.+?)ms waiting for another gitnexus analyze \\(pid (.+?) on (.+?), invocation (.+?)\\) to release the index lock\\.","errorType":"exception","errorClass":"IndexLockTimeoutError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/storage/index-lock.ts","lineNumber":486,"sourceCode":"      malformedSince = null;\n      if (isStale(holder)) {\n        opts.log?.(\n          `Reclaiming stale index lock from dead analyze (pid ${holder.pid}, ` +\n            `invocation ${holder.invocationId}).`,\n        );\n        stealLock(lockPath, me, holder); // reclaim ONLY this dead record; live locks are never stolen\n        continue;\n      }\n      // Live holder → wait.\n      if (!announcedWait) {\n        announcedWait = true;\n        opts.onWaitStart?.(holder);\n        opts.log?.(\n          `Another gitnexus analyze (pid ${holder.pid} on ${holder.hostname}) is ` +\n            `refreshing this index — waiting for it to finish.`,\n        );\n      }\n      if (waited >= timeoutMs) throw new IndexLockTimeoutError(holder, waited);\n      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","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/storage/index-lock.ts#L468-L504","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before — times out at the 10-minute default on a huge repo\n gitnexus analyze .\n\n# after\nGITNEXUS_INDEX_LOCK_TIMEOUT_MS=3600000 gitnexus analyze .","handlingStrategy":"retry","validationCode":"import { execSync } from 'node:child_process';\nfunction otherAnalyzeRunning(): boolean {\n  try {\n    const out = execSync('pgrep -af \"gitnexus analyze\" || true', { encoding: 'utf8' });\n    return out\n      .split('\\n')\n      .some((l) => l.trim() && !l.startsWith(String(process.pid)));\n  } catch {\n    return false;\n  }\n}\nif (otherAnalyzeRunning()) await waitForOtherAnalyze();","typeGuard":null,"tryCatchPattern":"try {\n  await runAnalyze();\n} catch (e) {\n  if (e?.name === 'IndexLockTimeoutError' && e.holder?.pid) {\n    console.warn(`holder pid ${e.holder.pid} on ${e.holder.hostname} — retrying once after it exits`);\n    await sleep(60_000);\n    return runAnalyze(); // one bounded retry, not a loop\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["concurrency","file-lock","indexing","timeout"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}