thedotmack/claude-mem · error · ChromaUnavailableError
Unable to remove stale Chroma writer lock at
Error message
Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)} What it means
Thrown by acquireChromaWriterLock when a live-owner lock was deemed stale (its PID is dead) and the attempt to delete the stale lock file with fs.rmSync failed. The manager cannot safely take ownership without removing the dead owner's lock, so it marks vector search unavailable and throws ChromaUnavailableError wrapping the underlying remove error.
Solutions
- Check and fix permissions on the lock file and its parent data dir so the current user can delete it (chown/chmod)
- If no chroma-mcp process is running, delete the stale lock file manually and retry
- On Windows, check for processes holding the lock file open (indexers, editors, antivirus) and close/reconfigure them
- If the data dir is on a read-only or full volume, move/chroma data dir config to a writable location
Example fix
// before: manager cannot remove stale lock
throw new ChromaUnavailableError(`Unable to remove stale Chroma writer lock at ${lockPath}: ...`);
// after: pre-flight cleanup by the caller with proper ownership
import fs from 'fs';
const lockPath = `${dataDir}/writer.lock`;
try { const p = JSON.parse(fs.readFileSync(lockPath,'utf-8')); if (!processExists(p.pid)) fs.unlinkSync(lockPath); } catch { /* retry via manager */ }
await manager.connect(); Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'fs';
function canRemoveStaleLock(lockPath: string): boolean {
try { fs.accessSync(path.dirname(lockPath), fs.constants.W_OK); return true; }
catch { return false; }
} Type guard
function isFileRemovalError(e: unknown): e is NodeJS.ErrnoException {
return e instanceof Error && ['EACCES','EPERM','EBUSY','EROFS'].includes((e as NodeJS.ErrnoException).code ?? '');
} Try / catch
try {
await manager.connect();
} catch (e) {
if (e instanceof ChromaUnavailableError && /Unable to remove stale Chroma writer lock/.test(e.message)) {
// check FS permissions / file holders, then manual cleanup before retry
console.error('Fix permissions on', lockPath, 'and remove it manually if no chroma process runs');
} else throw e;
} Prevention
- Run all Claude-mem instances as the same OS user so lock files stay deletable
- Keep the chroma data dir on a local writable volume, not a read-only or network mount
- Exclude the chroma data dir from antivirus/indexers that lock files on Windows
- After a crash, verify lock file permissions before restarting
When it happens
Trigger: EEXIST on lock write -> lock read succeeds -> isChromaWriterLockLive(existing) is false (owner PID no longer exists) -> fs.rmSync(lockPath) throws (EACCES/EPERM/EBUSY/EROFS/ENOENT edge).
Common situations: Lock file or parent data dir owned by another user (running as different user/sudo vs. previous session); read-only mount or immutable attribute on the lock file; antivirus/file-indexer on Windows holding the file open; filesystem permissions changed after a crash.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- Chroma data dir is already owned by PID ; refusing to start…
- Chroma writer lock at
- Unable to acquire Chroma writer lock at
- Unable to remove unreadable Chroma writer lock at
- Backfill failed
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/661686ea379a1774.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/ChromaMcpManager.ts:526
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 d8bc9755e7)