thedotmack/claude-mem · error · ChromaUnavailableError

Unable to acquire Chroma writer lock at ${lockPath}: ${error

Error message

Unable to acquire Chroma writer lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}

What it means

To prevent two processes writing the same local Chroma data dir simultaneously (SQLite-like corruption risk), acquireChromaWriterLock() atomically creates a .claude-mem-chroma-writer.lock via fs.writeFileSync flag 'wx'. If writeFileSync throws anything OTHER than EEXIST (e.g. EACCES, ENOSPC, ENOENT dir missing, EROFS), it records health and throws ChromaUnavailableError with the underlying message. EEXIST is handled separately by inspecting the existing lock.

Source

Thrown at src/services/sync/ChromaMcpManager.ts:418

      acquiredAt: new Date().toISOString(),
      startToken: captureProcessStartToken(process.pid),
    };

    for (let attempt = 0; attempt < 2; attempt += 1) {
      try {
        fs.writeFileSync(lockPath, JSON.stringify(payload, null, 2), {
          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', {

View on GitHub (pinned to d768ba3643)

Solutions

  1. Check the underlying message/errno in the error: fix the filesystem permission or space issue on the Chroma data dir.
  2. Ensure the user running the worker owns/can write the local Chroma data dir.
  3. Free disk space if ENOSPC; remount read-write if EROFS; relax MAC policy if it blocks the write.
  4. Retry the operation once the filesystem issue is resolved.

Example fix

# before
ls -la ~/.claude-mem/chroma/  # owned by root, worker runs as appuser
# acquireChromaWriterLock -> EACCES -> throws

# after
sudo chown -R appuser:appuser ~/.claude-mem/chroma
# restart worker
Defensive patterns

Strategy: try-catch

Validate before calling

import { access, constants } from 'fs/promises';
import path from 'path';

async function canWriteLock(dataDir: string): Promise<boolean> {
  try {
    await access(path.dirname(dataDir), constants.W_OK);
    return true;
  } catch { return false; }
}

Type guard

import { ChromaUnavailableError } from '../worker/search/errors.js';

function isWriterLockAcquireFailure(e: unknown): boolean {
  return e instanceof ChromaUnavailableError && /Unable to acquire Chroma writer lock/i.test(e.message);
}

Try / catch

try {
  await chromaManager.search(query);
} catch (e) {
  if (e instanceof ChromaUnavailableError && /Unable to acquire Chroma writer lock/i.test(e.message)) {
    // Filesystem-level issue (perms/space). Surface actionable guidance, degrade to FTS.
    logger.error('CHROMA', e.message, {}, e);
    return await ftsSearch(query);
  }
  throw e;
}

Prevention

When it happens

Trigger: The atomic lock create fails with a non-EEXIST errno: no write permission (EACCES) on the data dir, read-only filesystem (EROFS), disk full (ENOSPC), the data dir was deleted between mkdirSync and the write (ENOENT), or a path/encoding error.

Common situations: Data dir on a read-only mount or owned by another user; disk full; SELinux/AppArmor denying writes; the chroma data dir is on a network FS that rejects exclusive create; a prior crash left filesystem in a bad state.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/f2ea80451033c1b8. Report an issue: GitHub.