thedotmack/claude-mem · warning · ChromaUnavailableError

Chroma mutation queue is full

Error message

Chroma mutation queue is full (${this.pendingMutationCalls}/${this.maxPendingMutationCalls}); deferring "${toolName}" to a later backfill

What it means

ChromaMcpManager throttles mutation calls (add/update/delete to Chroma) through a serialized queue with a bounded number of pending operations. When pendingMutationCalls reaches maxPendingMutationCalls, further mutations are rejected with ChromaUnavailableError so they can be deferred to a later backfill instead of overwhelming the Chroma MCP server. This is deliberate backpressure, not a fault in the caller.

Solutions

  1. Wait for the in-flight queue to drain and retry the mutation; the deferred work is covered by a later backfill.
  2. Reduce concurrent producers of Chroma mutations (batch writes, serialize sync workers).
  3. Check Chroma MCP server health/latency; a slow server makes the bounded queue fill quickly.
  4. Raise maxPendingMutationCalls only if the Chroma server can absorb the extra load.
  5. Ensure a backfill pass runs afterwards (ChromaSync.ensureBackfilled) so deferred mutations are applied.

Example fix

// before: fire-and-forget bulk mutation loop
for (const doc of docs) await manager.mutate('add', doc);
// after: chunk and retry on ChromaUnavailableError
for (const chunk of chunked(docs, 100)) {
  await Promise.all(chunk.map(d => manager.mutate('add', d).catch(e => {
    if (e instanceof ChromaUnavailableError) deferred.push(d); else throw e;
  })));
  await backfillIfDeferred();
}
Defensive patterns

Strategy: retry

Validate before calling

// before calling a mutation
if (manager.getPendingMutationCount() >= manager.maxPendingMutationCalls) {
  await drainOrDeferToBackfill();
}

Try / catch

try {
  await manager.mutate('add', doc);
} catch (e) {
  if (e instanceof ChromaUnavailableError && /queue is full/.test(e.message)) {
    deferredDocs.push(doc); // re-applied by later backfill
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any Chroma mutation via ChromaMcpManager while maxPendingMutationCalls mutations are already queued awaiting the serialized mutationTail promise; typically bursts of writes during heavy sync activity.

Common situations: Bulk document ingest during initial backfill, rapid re-indexing after schema changes, or a slow/unresponsive Chroma MCP server causing queued mutations to pile up faster than they drain.

Related errors


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

Appendix: source

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

      if (parseError instanceof Error) {
        logger.debug('CHROMA_MCP', 'Non-JSON response from tool, returning null', {
          toolName,
          textPreview: firstTextContent.text.slice(0, 100)
        });
      }
      return null;
    }
  }

  private async enqueueMutation<T>(operation: () => Promise<T>, toolName: string): Promise<T> {
    if (this.pendingMutationCalls >= this.maxPendingMutationCalls) {
      const message = `Chroma mutation queue is full (${this.pendingMutationCalls}/${this.maxPendingMutationCalls}); deferring "${toolName}" to a later backfill`;
      logger.warn('CHROMA_MCP', message, {
        toolName,
        pendingMutations: this.pendingMutationCalls,
        maxPendingMutations: this.maxPendingMutationCalls
      });
      throw new ChromaUnavailableError(message);
    }

    this.pendingMutationCalls += 1;
    const enqueuedGeneration = this.connectionGeneration;
    const run = this.mutationTail
      .catch(() => undefined)
      .then(async () => {
        if (enqueuedGeneration !== this.connectionGeneration) {
          throw new ChromaMcpConnectionCancelledError('queued chroma-mcp mutation cancelled during shutdown');
        }
        return operation();
      });

    this.mutationTail = run.then(() => undefined, () => undefined);

    try {
      return await run;
    } finally {

View on GitHub (pinned to d8bc9755e7)