thedotmack/claude-mem · error · ChromaUnavailableError
Unable to remove unreadable Chroma writer lock at
Error message
Unable to remove unreadable Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)} What it means
When the existing Chroma writer lock file exists but cannot be read (readChromaWriterLock returns nothing usable), the code tries to remove it (fs.rmSync force) and retry. If that removal itself fails, it cannot safely reap a stale lock or safely refuse a second writer, so it records unavailability and throws ChromaUnavailableError with the path and remove error.
Solutions
- Manually delete the lock file at the path in the message (after confirming no chroma-mcp process is running), then retry.
- Fix ownership/permissions of the lock file and its directory (chown to the running user).
- Remount the volume read-write or move the data/lock directory to a writable filesystem.
- Check for stale locks from previous runs as root/other users and align the user that launches the app.
Example fix
// shell pgrep -f chroma-mcp || rm -f ~/.claude-mem/chroma/writer.lock # then restart
Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'fs';
function lockReadableAndRemovable(lockPath: string): boolean {
try {
fs.accessSync(lockPath, fs.constants.R_OK | fs.constants.W_OK);
return true;
} catch { return false; }
}
if (fs.existsSync(lockPath) && !lockReadableAndRemovable(lockPath)) {
logger.warn('Stale/unreadable Chroma writer lock needs manual cleanup', { lockPath });
} Try / catch
try {
await manager.ensureConnected();
} catch (e) {
if (e instanceof ChromaUnavailableError && e.message.includes('Unable to remove unreadable Chroma writer lock')) {
logger.error('Remove the lock manually after verifying no writer is running', { cause: e.cause ?? e.message });
} else throw e;
} Prevention
- Always launch the app as the same user so leftover locks remain owned/removable.
- Avoid running under sudo or mixed users against the same data directory.
- On container restarts, ensure the lock volume is remounted read-write.
- Add cleanup of stale lock files to shutdown/startup scripts.
When it happens
Trigger: acquireChromaWriterLock finds a leftover lock file that is unreadable (wrong owner, permission bits, corrupted JSON owner info) and the rmSync cleanup throws due to permissions, immutable flag, or filesystem errors.
Common situations: A previous crash left a lock file owned by a different user (e.g. created under sudo or another container user); the lock file sits on a read-only mount after a container restart; NFS/overlayfs quirks making the file unreadable and unremovable.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Unable to acquire Chroma writer lock at
- [ide-detection] Failed to read VS Code extensions directory:
- installPluginDependencies: no package.json at
- Unable to remove stale Chroma writer lock at
- [uninstall] Could not read
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/81267d46e8d86355.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/ChromaMcpManager.ts:501
const message = `Unable to acquire Chroma writer lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}`;
recordChromaVectorSearchUnavailable(message);
throw new ChromaUnavailableError(message, error instanceof Error ? error : undefined);
}
const existing = ChromaMcpManager.readChromaWriterLock(lockPath);
if (!existing) {
// Without a readable owner the liveness reaper below can never run,
// so vector sync would stay dead until someone deletes the file (#3916).
// Treat an unreadable lock that is past the write grace period as stale.
if (ChromaMcpManager.isChromaWriterLockAbandoned(lockPath)) {
try {
fs.rmSync(lockPath, { force: true });
logger.info('CHROMA_MCP', 'Removed unreadable Chroma writer lock', { lockPath });
continue;
} catch (removeError) {
const message = `Unable to remove unreadable 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 writer lock at ${lockPath} is unreadable; refusing to start a second writer`;
recordChromaVectorSearchUnavailable(message);
throw new ChromaUnavailableError(message);
}
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,View on GitHub (pinned to d8bc9755e7)