thedotmack/claude-mem · error · ChromaUnavailableError

Unable to acquire Chroma writer lock at ${lockPath} after re

Error message

Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock

What it means

Thrown after the 2-iteration acquireChromaWriterLock loop completes without success. The loop tries fs.writeFileSync(flag:'wx'); on EEXIST it reads the lock, and if the lock is stale it removes it and continues for a second attempt. If after that second attempt the lock still cannot be freshly created (e.g. another process re-grabbed it in the race window, or removal 'succeeded' but the wx write still hit EEXIST), control falls through to this terminal ChromaUnavailableError.

Source

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

              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', {
        lockPath: lock.path,
      });
      return;
    }

    if (existing.pid !== process.pid || existing.ownerId !== lock.ownerId) {

View on GitHub (pinned to d768ba3643)

Solutions

  1. Reduce concurrency: ensure only one ChromaMcpManager instance is starting against the directory at a time.
  2. Retry the operation after a short backoff once the contending writer has settled, since this is inherently a transient race.
  3. Inspect the lock file at lockPath after the failure to see which pid/ownerId currently holds it, and resolve that process first.
  4. Give each concurrent worker its own data directory to eliminate the contention entirely.

Example fix

// before: retried synchronously in a tight spawn loop
await manager.ensureConnected();
// after: back off and let the contending writer settle
for (let attempt = 0; attempt < 5; attempt++) {
  try { await manager.ensureConnected(); break; }
  catch (e) { if (e instanceof ChromaUnavailableError) await sleep(500 * (attempt+1)); else throw e; }
}
Defensive patterns

Strategy: retry

Type guard

function isLockAcquireExhausted(e: unknown): boolean {
  return e instanceof Error && /Unable to acquire Chroma writer lock.*after removing stale lock/i.test(e.message);
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try { await manager.ensureConnected(); break; }
  catch (e) {
    if (isLockAcquireExhausted(e)) { await sleep(500 * (attempt + 1)); continue; }
    throw e;
  }
}

Prevention

When it happens

Trigger: Both loop attempts failed: the first removed a stale lock and continued, but the second attempt's writeFileSync(wx) again threw EEXIST because a live process reacquired the lock in between, or the stale-lock removal did not actually clear the path. This is the loop-exhausted fallthrough.

Common situations: High contention: two managers racing to reap and reacquire the same stale lock within milliseconds; a supervisor rapidly respawning the worker so each spawn re-finds an EEXIST lock owned by the just-started sibling; filesystem where rmSync reports success but the file is recreated by an antivirus/quarantine agent.

Related errors


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