thedotmack/claude-mem · error · ChromaUnavailableError

Chroma data dir is already owned by PID ; refusing to start…

Error message

Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer

What it means

Thrown by acquireChromaWriterLock when the lock file exists, is readable, and its recorded owner PID is still alive but belongs to a different process (and a different ownerId), meaning another running process already owns the Chroma data dir. Starting a second persistent chroma-mcp writer against the same data dir would corrupt it, so the manager refuses, marks vector search unavailable, and throws ChromaUnavailableError.

Solutions

  1. Find the owning process (PID is in the message) and stop it: kill <pid> or close the other Claude-mem/editor instance
  2. If the PID shown is actually dead or wrong (recycled PID), delete the lock file and retry - the liveness check misread the process
  3. Configure different chroma data dirs per concurrent instance so each has a single writer
  4. Ensure only one Claude-mem plugin instance/entry point is active at a time (disable duplicates in the marketplace config)

Example fix

// before: two managers share one data dir
const shared = '~/.claude-mem/chroma';
const a = new ChromaMcpManager(shared); await a.connect();
const b = new ChromaMcpManager(shared); await b.connect(); // throws
// after: instance-local data dir or reuse one manager
const mgr = new ChromaMcpManager(shared);
await mgr.connect(); // single writer
// or per-instance:
const dir = path.join(os.tmpdir(), `claude-mem-chroma-${process.pid}`);
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from 'fs';
function chromaDataDirIsOwned(dataDir: string): number | null {
  try {
    const p = JSON.parse(fs.readFileSync(`${dataDir}/writer.lock`, 'utf-8'));
    try { process.kill(p.pid, 0); return p.pid; } catch { return null; }
  } catch { return null; }
}

Type guard

function isChromaUnavailableError(e: unknown): e is ChromaUnavailableError {
  return e instanceof ChromaUnavailableError;
}

Try / catch

try {
  await manager.connect();
} catch (e) {
  if (e instanceof ChromaUnavailableError && /already owned by PID/.test(e.message)) {
    const pid = Number(/PID (\d+)/.exec(e.message)?.[1]);
    // stop the duplicate instance or reuse the existing manager instead of starting a second writer
  } else throw e;
}

Prevention

When it happens

Trigger: Two Claude-mem instances (e.g., two editor windows, a CLI plus an IDE, or a leaked chroma-mcp child from a prior session) both call connectInternal with the same chroma data dir; the first holds the live writer lock and the second sees EEXIST -> live PID != own PID.

Common situations: Duplicate Claude-mem plugin instances configured with the same CHROMA data dir; a leftover chroma-mcp prewarm child that outlived its parent; running dev build and installed build concurrently; shared data dir across multiple machines on a network drive.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/22de02662aacb98b. Report an issue: GitHub.

Appendix: source

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

        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;
    }
    this.chromaWriterLock = null;

    const existing = ChromaMcpManager.readChromaWriterLock(lock.path);
    if (!existing) {
      logger.debug('CHROMA_MCP', 'Chroma writer lock already missing or unreadable during release', {

View on GitHub (pinned to d8bc9755e7)