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

  1. Manually delete the lock file at the path in the message (after confirming no chroma-mcp process is running), then retry.
  2. Fix ownership/permissions of the lock file and its directory (chown to the running user).
  3. Remount the volume read-write or move the data/lock directory to a writable filesystem.
  4. 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

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


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)