thedotmack/claude-mem · error · ChromaUnavailableError
Chroma data dir is already owned by PID ; refusing to start…
Error message
Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer What it means
Thrown by acquireChromaWriterLock when the lock file exists, is readable, and its recorded owner PID is still alive but belongs to a different process (and a different ownerId), meaning another running process already owns the Chroma data dir. Starting a second persistent chroma-mcp writer against the same data dir would corrupt it, so the manager refuses, marks vector search unavailable, and throws ChromaUnavailableError.
Solutions
- Find the owning process (PID is in the message) and stop it: kill <pid> or close the other Claude-mem/editor instance
- If the PID shown is actually dead or wrong (recycled PID), delete the lock file and retry - the liveness check misread the process
- Configure different chroma data dirs per concurrent instance so each has a single writer
- Ensure only one Claude-mem plugin instance/entry point is active at a time (disable duplicates in the marketplace config)
Example fix
// before: two managers share one data dir
const shared = '~/.claude-mem/chroma';
const a = new ChromaMcpManager(shared); await a.connect();
const b = new ChromaMcpManager(shared); await b.connect(); // throws
// after: instance-local data dir or reuse one manager
const mgr = new ChromaMcpManager(shared);
await mgr.connect(); // single writer
// or per-instance:
const dir = path.join(os.tmpdir(), `claude-mem-chroma-${process.pid}`); Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'fs';
function chromaDataDirIsOwned(dataDir: string): number | null {
try {
const p = JSON.parse(fs.readFileSync(`${dataDir}/writer.lock`, 'utf-8'));
try { process.kill(p.pid, 0); return p.pid; } catch { return null; }
} catch { return null; }
} Type guard
function isChromaUnavailableError(e: unknown): e is ChromaUnavailableError {
return e instanceof ChromaUnavailableError;
} Try / catch
try {
await manager.connect();
} catch (e) {
if (e instanceof ChromaUnavailableError && /already owned by PID/.test(e.message)) {
const pid = Number(/PID (\d+)/.exec(e.message)?.[1]);
// stop the duplicate instance or reuse the existing manager instead of starting a second writer
} else throw e;
} Prevention
- Run exactly one Claude-mem instance per chroma data dir - disable duplicate plugin copies (dev vs installed)
- Reuse a single ChromaMcpManager/singleton instead of constructing new ones per call site
- Point concurrent instances at separate data dirs
- Check for orphaned chroma-mcp children after abnormal exits (ps aux | grep chroma-mcp)
When it happens
Trigger: Two Claude-mem instances (e.g., two editor windows, a CLI plus an IDE, or a leaked chroma-mcp child from a prior session) both call connectInternal with the same chroma data dir; the first holds the live writer lock and the second sees EEXIST -> live PID != own PID.
Common situations: Duplicate Claude-mem plugin instances configured with the same CHROMA data dir; a leftover chroma-mcp prewarm child that outlived its parent; running dev build and installed build concurrently; shared data dir across multiple machines on a network drive.
Related errors
- Chroma writer lock at
- Unable to acquire Chroma writer lock at
- Unable to remove stale Chroma writer lock at
- Backfill failed
- chroma-mcp call cancelled during shutdown
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/22de02662aacb98b.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/ChromaMcpManager.ts:532
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 d8bc9755e7)