thedotmack/claude-mem · error · ChromaUnavailableError
Chroma data dir ${normalizedDataDir} is already owned by PID
Error message
Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer What it means
Thrown when acquiring the Chroma writer lock fails with EEXIST, the existing lock is readable AND its owner PID is still alive (isChromaWriterLockLive true) AND it does not belong to this manager (different pid/ownerId). The manager deliberately refuses to start a second writer against the same data directory because SQLite/Chroma require single-writer access. Surfaced as ChromaUnavailableError (503, CHROMA_UNAVAILABLE).
Source
Thrown at src/services/sync/ChromaMcpManager.ts:451
if (!ChromaMcpManager.isChromaWriterLockLive(existing)) {
try {
fs.rmSync(lockPath, { force: true });
logger.info('CHROMA_MCP', 'Removed stale Chroma writer lock', {
lockPath,
priorPid: existing.pid,
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', {View on GitHub (pinned to d768ba3643)
Solutions
- Identify the owning process: read existing.pid from the log and run `ps -p <pid>` (or the value embedded in the message) to see what it is.
- Shut down the other legitimate writer (close the second editor/session, stop the duplicate worker) and let it release the lock cleanly.
- If that PID is a zombie/orphaned chroma-mcp uvx child from a crashed parent, kill it (kill <pid> or kill the process tree) then retry.
- If you intentionally need parallel processes, give each its own data directory (separate CHROMA data dir per instance) instead of sharing one.
- As a last resort, confirm the PID is truly gone, then delete the lock file at the lockPath manually.
Example fix
// before: two instances share one data dir
new ChromaMcpManager({ dataDir: '~/.claude-mem/chroma' });
// after: each instance gets an isolated data dir
new ChromaMcpManager({ dataDir: `~/.claude-mem/chroma-${process.env.WORKER_ID}` }); Defensive patterns
Strategy: validation
Validate before calling
// Before connecting, check whether another live process owns the lock
function isLockHeldByLiveOther(lockPath: string): boolean {
try {
const raw = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
if (typeof raw.pid !== 'number') return false;
return isPidAlive(raw.pid) && raw.pid !== process.pid;
} catch { return false; }
} Type guard
function isSecondWriterRefusal(e: unknown): boolean {
return e instanceof Error && /already owned by PID.*refusing to start a second writer/i.test(e.message);
} Try / catch
try { await manager.ensureConnected(); }
catch (e) {
if (isSecondWriterRefusal(e)) {
// The other PID is legitimate; degrade vector search rather than double-writing
markVectorSearchUnavailable(e.message);
return;
}
throw e;
} Prevention
- Run a single ChromaMcpManager per data directory; give each concurrent worker its own data dir.
- Ensure prior sessions/workers shut down cleanly so they release the lock.
- Use a process supervisor that stops the old chroma-mcp child before starting a new one.
- Never share a Chroma data dir across two editor sessions.
When it happens
Trigger: acquireChromaWriterLock finds an EEXIST lock whose pid is alive and ownerId differs; this happens when another plugin instance, a parallel worker process, or a previous session that did not release its lock is actively using ${normalizedDataDir}.
Common situations: Two editor windows / two Claude Code instances both pointing at the same ~/.claude-mem/chroma data dir; a long-running worker that crashed the JS process but whose chroma-mcp uvx subprocess is still alive holding the lock; running under a process supervisor that auto-restarted the worker while the old chroma-mcp child still owns the directory.
Related errors
- Unable to acquire Chroma writer lock at ${lockPath} after re
- 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/22de02662aacb98b.
Report an issue: GitHub.