thedotmack/claude-mem · info · ChromaMcpConnectionCancelledError
queued chroma-mcp mutation cancelled during shutdown
Error message
queued chroma-mcp mutation cancelled during shutdown
What it means
Mutations enqueued in ChromaMcpManager capture the connectionGeneration at enqueue time. When the queued promise finally runs, if the generation has advanced (meaning shutdown or reconnect occurred), the stale operation is aborted with ChromaMcpConnectionCancelledError rather than being executed against a torn-down or replaced connection. This prevents mutations leaking across connection lifecycles.
Solutions
- Catch ChromaMcpConnectionCancelledError and treat the mutation as not-applied; re-issue it after the new connection is ready.
- Ensure a backfill runs after restart so cancelled mutations are re-applied.
- Gate mutation calls on manager connection state (skip enqueueing during shutdown).
- Verify only one manager instance owns the connection; concurrent shutdown/restart of two instances races generations.
Example fix
// before
await manager.mutate('update', doc);
// after
try {
await manager.mutate('update', doc);
} catch (e) {
if (e instanceof ChromaMcpConnectionCancelledError) {
await manager.waitUntilConnected();
await manager.mutate('update', doc);
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// skip enqueueing during teardown
if (!manager.isConnected() || manager.isShuttingDown()) {
await deferToBackfill(operation);
return;
} Type guard
function isConnectionCancelled(e: unknown): e is ChromaMcpConnectionCancelledError {
return e instanceof ChromaMcpConnectionCancelledError;
} Try / catch
try {
await manager.mutate('update', doc);
} catch (e) {
if (isConnectionCancelled(e)) {
retryQueue.push(doc); // re-issue on next connection
} else throw e;
} Prevention
- Gate write paths on connection state; don't enqueue during shutdown
- Always run a backfill after restart so cancelled mutations are re-applied
- Handle ChromaMcpConnectionCancelledError explicitly wherever mutations are issued
- Avoid multiple manager instances sharing one Chroma connection
When it happens
Trigger: A mutation is waiting in mutationTail when ChromaMcpManager shuts down or reconnects (connectionGeneration increments); when the queued promise resumes it detects enqueuedGeneration !== connectionGeneration and throws.
Common situations: App shutdown with sync still writing, Chroma MCP server restart forcing reconnection, or a connection reset while many mutations are still queued.
Related errors
- chroma-mcp call cancelled during shutdown
- chroma-mcp connection cancelled during shutdown
- failed to kill in-flight chroma-mcp prewarm tree…
- failed to kill prior chroma-mcp tree (best-effort)
- Local Chroma mutations are unavailable after shutdown begins
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/c0d2b8b7968b326a.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/ChromaMcpManager.ts:900
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 {
this.pendingMutationCalls -= 1;
}
}
private static isMutationTool(toolName: string): boolean {
return CHROMA_MUTATION_TOOL_PATTERN.test(toolName);
}
async isHealthy(): Promise<boolean> {View on GitHub (pinned to d8bc9755e7)