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

  1. Check and fix permissions on the lock file and its parent data dir so the current user can delete it (chown/chmod)
  2. If no chroma-mcp process is running, delete the stale lock file manually and retry
  3. On Windows, check for processes holding the lock file open (indexers, editors, antivirus) and close/reconfigure them
  4. 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

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


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)