thedotmack/claude-mem · error · ChromaUnavailableError
Unable to remove stale Chroma writer lock at ${lockPath}: ${
Error message
Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)} What it means
Thrown by ChromaMcpManager.acquireChromaWriterLock after it detected an existing writer lock whose owning PID is dead (isChromaWriterLockLive returned false) but fs.rmSync(lockPath, { force: true }) still failed to delete the stale lock file. It is surfaced as a ChromaUnavailableError (HTTP 503, code CHROMA_UNAVAILABLE) via recordChromaVectorSearchUnavailable, so semantic/vector search is treated as down. The lock is the single-writer guard over the Chroma data directory, so the manager refuses to proceed without removing it.
Source
Thrown at src/services/sync/ChromaMcpManager.ts:445
if (existing.pid === process.pid && existing.ownerId === this.chromaWriterOwnerId) {
this.chromaWriterLock = { path: lockPath, dataDir: normalizedDataDir, ownerId: this.chromaWriterOwnerId };
return;
}
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;View on GitHub (pinned to d768ba3643)
Solutions
- Check the lockPath in the message and inspect filesystem permissions: ensure the process user owns or has write/delete rights on the directory containing the lock file.
- Look for the prior pid in the log (priorPid field) and confirm no stray chroma/uvx/python process still holds the file open; kill it if present.
- Remove the stale lock file manually (rm the path from the message) once you confirm the PID is truly dead, then retry.
- If on a read-only / network mount, move the Chroma data dir (CHROMA data dir / configured path) to a local writable filesystem.
- Disable any antivirus/SELinux policy that locks files in the Chroma directory, or add an exclusion for the lock path.
Example fix
// before: lock sits on a read-only mount const lockPath = path.join(readOnlyDataDir, '.chroma-writer-lock.json'); // after: data dir on a local writable volume const lockPath = path.join(os.homedir(), '.claude-mem', 'chroma', '.chroma-writer-lock.json');
Defensive patterns
Strategy: try-catch
Validate before calling
// Before starting the manager, confirm the lock dir is writable+deletable
import fs from 'node:fs';
function canManageLock(lockPath: string): boolean {
try {
fs.accessSync(lockPath, fs.constants.W_OK);
// try a temp create+delete in the same dir
const probe = lockPath + '.probe-' + process.pid;
fs.writeFileSync(probe, 'x', { flag: 'wx' });
fs.rmSync(probe, { force: true });
return true;
} catch { return false; }
} Type guard
import { ChromaUnavailableError } from '...';
function isChromaUnavailable(e: unknown): e is ChromaUnavailableError {
return e instanceof ChromaUnavailableError || (e instanceof Error && /stale Chroma writer lock|already owned by PID|Unable to acquire Chroma writer lock/i.test(e.message));
} Try / catch
try {
await manager.ensureConnected();
} catch (e) {
if (isChromaUnavailable(e) && /remove stale Chroma writer lock/i.test(e.message)) {
// surface a 'vector search unavailable' state to the UI; do not crash the worker
markVectorSearchUnavailable(e.message);
return;
}
throw e;
} Prevention
- Run the plugin under a user that owns the Chroma data directory and can delete files within it.
- Keep the Chroma data dir on a local writable filesystem, not a read-only or network mount.
- Configure antivirus/SELinux exclusions for the lock file path.
- Ensure clean shutdown so releaseChromaWriterLock runs instead of leaving stale locks.
When it happens
Trigger: Called during ensureConnected/prewarm when acquiring the writer lock at ${lockPath}; readChromaWriterLock returns a payload whose pid is no longer alive, then fs.rmSync throws (EACCES, EBUSY, read-only filesystem, antivirus/SELinux hold, or the path was removed by a race between isChromaWriterLockLive and rmSync).
Common situations: Running the plugin under a restricted user without delete permission on the Chroma data directory; the data dir living on a read-only or network mount (NFS/SMB) that rejects rmSync; an OS-level file lock (Windows Defender, SELinux, open file handle from a crashed process) preventing removal; two managers racing to reap the same stale lock.
Related errors
- Unable to acquire Chroma writer lock at ${lockPath}: ${error
- Chroma writer lock at ${lockPath} is unreadable; refusing to
- uvx executable not found for chroma-mcp (${uvxSpawnCommand})
- Chroma data dir ${normalizedDataDir} is already owned by PID
- Unable to acquire Chroma writer lock at ${lockPath} after re
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/661686ea379a1774.
Report an issue: GitHub.