thedotmack/claude-mem · error · ChromaUnavailableError
Chroma writer lock at ${lockPath} is unreadable; refusing to
Error message
Chroma writer lock at ${lockPath} is unreadable; refusing to start a second writer What it means
When the lock file already exists (EEXIST), acquireChromaWriterLock reads and validates it via readChromaWriterLock(). If that returns null — the file is present but unreadable (corrupt JSON, wrong schema, missing fields) — the manager refuses to start a second writer and throws ChromaUnavailableError rather than guess whether the existing owner is live. This avoids concurrent-writer corruption when the lock state cannot be trusted.
Source
Thrown at src/services/sync/ChromaMcpManager.ts:425
encoding: 'utf-8',
flag: 'wx',
});
this.chromaWriterLock = { path: lockPath, dataDir: normalizedDataDir, ownerId: this.chromaWriterOwnerId };
logger.debug('CHROMA_MCP', 'Acquired Chroma writer lock', { lockPath, dataDir: normalizedDataDir });
return;
} catch (error) {
const errno = error instanceof Error ? (error as NodeJS.ErrnoException).code : undefined;
if (errno !== 'EEXIST') {
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) {
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,
priorStartedAt: existing.acquiredAt,
});
continue;
} catch (removeError) {
const message = `Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}`;View on GitHub (pinned to d768ba3643)
Solutions
- Inspect the lock file contents at the path in the message; if it is clearly stale/corrupt and no claude-mem/chroma process is running, delete it manually.
- Verify no live process owns it first (check the pid field if partially readable, and `ps -p <pid>`), then remove the file.
- Restart the worker so it re-acquires a clean lock.
- If this recurs, find what is corrupting the lock file (concurrent runs, disk issue).
Example fix
# before — corrupt lock, no live chroma process ps aux | grep chroma # none running ls -la ~/.claude-mem/chroma/.claude-mem-chroma-writer.lock # after — remove the unreadable lock, restart rm ~/.claude-mem/chroma/.claude-mem-chroma-writer.lock # restart the claude-mem worker
Defensive patterns
Strategy: try-catch
Validate before calling
import { readFileSync, existsSync } from 'fs';
function lockReadable(lockPath: string): boolean {
if (!existsSync(lockPath)) return true; // absent is fine
try {
const raw = JSON.parse(readFileSync(lockPath, 'utf-8'));
return typeof raw.pid === 'number' && typeof raw.ownerId === 'string';
} catch { return false; }
} Type guard
import { ChromaUnavailableError } from '../worker/search/errors.js';
function isLockUnreadable(e: unknown): boolean {
return e instanceof ChromaUnavailableError && /lock .* is unreadable/i.test(e.message);
} Try / catch
import { rmSync } from 'fs';
try {
await chromaManager.search(query);
} catch (e) {
if (e instanceof ChromaUnavailableError && /is unreadable/i.test(e.message)) {
// Only safe if no live chroma process holds it.
const lockPath = /at (.+\.claude-mem-chroma-writer\.lock)/.exec(e.message)?.[1];
if (lockPath && noLiveChromaProcess()) {
rmSync(lockPath, { force: true });
return await chromaManager.search(query); // one retry after cleanup
}
}
throw e;
} Prevention
- Before deleting a lock file, confirm no live chroma/claude-mem process owns it (ps -p <pid>).
- Investigate recurring lock corruption — it implies concurrent writers or disk faults.
- Stop the worker cleanly so it releases its lock on exit.
When it happens
Trigger: The lock file .claude-mem-chroma-writer.lock exists in the data dir but cannot be parsed into a valid ChromaWriterLockPayload: contents are not JSON, are JSON but missing/invalid pid/ownerId/dataDir/acquiredAt fields, or the file is empty/garbage.
Common situations: A previous process crashed mid-write leaving a truncated lock file; someone hand-edited or emptied the lock file; a different/older claude-mem version wrote a schema the reader rejects; disk corruption touched the lock file.
Related errors
- Unable to acquire Chroma writer lock at ${lockPath}: ${error
- Unable to remove stale Chroma writer lock at ${lockPath}: ${
- 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/3a4b89fd981d9aa7.
Report an issue: GitHub.