{"record":{"id":"27ec614fcd3a21b5","repo":"thedotmack/claude-mem","slug":"unable-to-acquire-chroma-writer-lock-at-lockpath-27ec61","errorCode":null,"errorMessage":"Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock","messagePattern":"Unable to acquire Chroma writer lock at (.+?) after removing stale lock","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"error","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":457,"sourceCode":"              priorStartedAt: existing.acquiredAt,\n            });\n            continue;\n          } catch (removeError) {\n            const message = `Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}`;\n            recordChromaVectorSearchUnavailable(message);\n            throw new ChromaUnavailableError(message, removeError instanceof Error ? removeError : undefined);\n          }\n        }\n\n        const message = `Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer`;\n        recordChromaVectorSearchUnavailable(message);\n        throw new ChromaUnavailableError(message);\n      }\n    }\n\n    const message = `Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock`;\n    recordChromaVectorSearchUnavailable(message);\n    throw new ChromaUnavailableError(message);\n  }\n\n  private releaseChromaWriterLock(): void {\n    const lock = this.chromaWriterLock;\n    if (!lock) {\n      return;\n    }\n    this.chromaWriterLock = null;\n\n    const existing = ChromaMcpManager.readChromaWriterLock(lock.path);\n    if (!existing) {\n      logger.debug('CHROMA_MCP', 'Chroma writer lock already missing or unreadable during release', {\n        lockPath: lock.path,\n      });\n      return;\n    }\n\n    if (existing.pid !== process.pid || existing.ownerId !== lock.ownerId) {","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/services/sync/ChromaMcpManager.ts#L439-L475","documentation":"Thrown after the 2-iteration acquireChromaWriterLock loop completes without success. The loop tries fs.writeFileSync(flag:'wx'); on EEXIST it reads the lock, and if the lock is stale it removes it and continues for a second attempt. If after that second attempt the lock still cannot be freshly created (e.g. another process re-grabbed it in the race window, or removal 'succeeded' but the wx write still hit EEXIST), control falls through to this terminal ChromaUnavailableError.","triggerScenarios":"Both loop attempts failed: the first removed a stale lock and continued, but the second attempt's writeFileSync(wx) again threw EEXIST because a live process reacquired the lock in between, or the stale-lock removal did not actually clear the path. This is the loop-exhausted fallthrough.","commonSituations":"High contention: two managers racing to reap and reacquire the same stale lock within milliseconds; a supervisor rapidly respawning the worker so each spawn re-finds an EEXIST lock owned by the just-started sibling; filesystem where rmSync reports success but the file is recreated by an antivirus/quarantine agent.","solutions":["Reduce concurrency: ensure only one ChromaMcpManager instance is starting against the directory at a time.","Retry the operation after a short backoff once the contending writer has settled, since this is inherently a transient race.","Inspect the lock file at lockPath after the failure to see which pid/ownerId currently holds it, and resolve that process first.","Give each concurrent worker its own data directory to eliminate the contention entirely."],"exampleFix":"// before: retried synchronously in a tight spawn loop\nawait manager.ensureConnected();\n// after: back off and let the contending writer settle\nfor (let attempt = 0; attempt < 5; attempt++) {\n  try { await manager.ensureConnected(); break; }\n  catch (e) { if (e instanceof ChromaUnavailableError) await sleep(500 * (attempt+1)); else throw e; }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isLockAcquireExhausted(e: unknown): boolean {\n  return e instanceof Error && /Unable to acquire Chroma writer lock.*after removing stale lock/i.test(e.message);\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try { await manager.ensureConnected(); break; }\n  catch (e) {\n    if (isLockAcquireExhausted(e)) { await sleep(500 * (attempt + 1)); continue; }\n    throw e;\n  }\n}","preventionTips":["Serialize manager start-up so only one process contends for the lock at a time.","Use isolated data directories for parallel workers to remove the race entirely.","Back off before retrying so a contending writer can settle.","Log the lock file's current owner on each failure to diagnose the contender."],"tags":["chroma","lock","concurrency","race-condition","vector-search"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}