thedotmack/claude-mem · error · ChromaUnavailableError
Unable to acquire Chroma writer lock at ${lockPath} after re
Error message
Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock What it means
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.
Source
Thrown at src/services/sync/ChromaMcpManager.ts:457
priorStartedAt: existing.acquiredAt,
});
continue;
} catch (removeError) {
const message = `Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}`;
recordChromaVectorSearchUnavailable(message);
throw new ChromaUnavailableError(message, removeError instanceof Error ? removeError : undefined);
}
}
const message = `Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer`;
recordChromaVectorSearchUnavailable(message);
throw new ChromaUnavailableError(message);
}
}
const message = `Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock`;
recordChromaVectorSearchUnavailable(message);
throw new ChromaUnavailableError(message);
}
private releaseChromaWriterLock(): void {
const lock = this.chromaWriterLock;
if (!lock) {
return;
}
this.chromaWriterLock = null;
const existing = ChromaMcpManager.readChromaWriterLock(lock.path);
if (!existing) {
logger.debug('CHROMA_MCP', 'Chroma writer lock already missing or unreadable during release', {
lockPath: lock.path,
});
return;
}
if (existing.pid !== process.pid || existing.ownerId !== lock.ownerId) {View on GitHub (pinned to d768ba3643)
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.
Example fix
// before: retried synchronously in a tight spawn loop
await manager.ensureConnected();
// after: back off and let the contending writer settle
for (let attempt = 0; attempt < 5; attempt++) {
try { await manager.ensureConnected(); break; }
catch (e) { if (e instanceof ChromaUnavailableError) await sleep(500 * (attempt+1)); else throw e; }
} Defensive patterns
Strategy: retry
Type guard
function isLockAcquireExhausted(e: unknown): boolean {
return e instanceof Error && /Unable to acquire Chroma writer lock.*after removing stale lock/i.test(e.message);
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try { await manager.ensureConnected(); break; }
catch (e) {
if (isLockAcquireExhausted(e)) { await sleep(500 * (attempt + 1)); continue; }
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Chroma data dir ${normalizedDataDir} is already owned by PID
- Unable to acquire Chroma writer lock at ${lockPath}: ${error
- Chroma writer lock at ${lockPath} is unreadable; refusing to
- Unable to remove stale Chroma writer lock at ${lockPath}: ${
- chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BAC
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/27ec614fcd3a21b5.
Report an issue: GitHub.