{"record":{"id":"6b24ae52eae218ba","repo":"thedotmack/claude-mem","slug":"local-chroma-mutations-are-unavailable-after-shutdown-begins","errorCode":null,"errorMessage":"Local Chroma mutations are unavailable after shutdown begins","messagePattern":"Local Chroma mutations are unavailable after shutdown begins","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"warning","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":805,"sourceCode":"      recordUvxVectorSearchUnavailable(unavailableMessage);\n      throw new ChromaUnavailableError(unavailableMessage, error instanceof Error ? error : undefined);\n    } finally {\n      if (timeoutId) {\n        clearTimeout(timeoutId);\n      }\n      if (this.activePrewarmChild === child) {\n        this.activePrewarmChild = null;\n        this.activePrewarmTracked = null;\n      }\n    }\n  }\n\n  async callTool(toolName: string, toolArguments: Record<string, unknown>): Promise<unknown> {\n    if (!this.serializeMutations || !ChromaMcpManager.isMutationTool(toolName)) {\n      return this.callToolUnqueued(toolName, toolArguments);\n    }\n    if (!this.acceptingLocalMutations) {\n      throw new ChromaUnavailableError('Local Chroma mutations are unavailable after shutdown begins');\n    }\n\n    return this.enqueueMutation(() => this.callToolUnqueued(toolName, toolArguments), toolName);\n  }\n\n  private async callToolUnqueued(toolName: string, toolArguments: Record<string, unknown>): Promise<unknown> {\n    const callGeneration = this.connectionGeneration;\n    await this.ensureConnected();\n\n    logger.debug('CHROMA_MCP', `Calling tool: ${toolName}`, {\n      arguments: JSON.stringify(toolArguments).slice(0, 200)\n    });\n\n    let result;\n    try {\n      result = await this.client!.callTool({\n        name: toolName,\n        arguments: toolArguments","sourceCodeStart":787,"sourceCodeEnd":823,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaMcpManager.ts#L787-L823","documentation":"ChromaUnavailableError thrown by ChromaMcpManager.callTool when a mutation tool (add/update/delete on local Chroma) is invoked after shutdown has begun. Once shutdown starts, acceptingLocalMutations is flipped to false, so local vector-store writes are no longer accepted — reads and non-mutation tools still go through unqueued. The library throws instead of queueing so writes are not silently lost or executed against a dying subprocess.","triggerScenarios":"Calling callTool() with a mutation tool name (per ChromaMcpManager.isMutationTool, e.g. chroma_add_documents / chroma_update_documents / chroma_delete_documents) while serializeMutations is enabled and shutdown of the manager has already started (acceptingLocalMutations === false).","commonSituations":"App shutdown/teardown racing with a sync pipeline that still flushes document mutations; worker stop hooks firing while an in-flight indexing job enqueues writes; tests tearing down the manager before background backfill finishes.","solutions":["Wait for all mutation calls to complete before calling shutdown/dispose on ChromaMcpManager (await pending sync/backfill promises first).","Check the shutdown flag before calling: expose or track acceptingLocalMutations state and skip/defer mutations when it is false.","Persist the pending mutations and replay them in a later backfill run after the manager is restarted.","Route mutations through the queue (enqueueMutation) during normal operation so they are either executed or deferred before shutdown, not rejected.","Catch ChromaUnavailableError and treat it as a deferral signal rather than a data loss error."],"exampleFix":"// before\nawait Promise.all([chromaManager.callTool('chroma_delete_documents', { ids })]);\nawait chromaManager.shutdown();\n// after\nawait chromaManager.shutdown();\n// mutations during shutdown are rejected; run them on a fresh manager\nconst freshManager = await ChromaMcpManager.create(config);\nawait freshManager.callTool('chroma_delete_documents', { ids });","handlingStrategy":"try-catch","validationCode":"// Track manager lifecycle; only call mutations while running\nif (!manager.isAcceptingMutations?.()) {\n  console.warn('chroma mutations rejected: shutdown in progress');\n} else {\n  await manager.callTool('chroma_add_documents', args);\n}","typeGuard":"function isMutationAllowed(manager: { callTool(t: string, a: Record<string, unknown>): Promise<unknown> }, toolName: string): boolean {\n  return !['chroma_add_documents','chroma_update_documents','chroma_delete_documents'].includes(toolName) || !managerIsShuttingDown;\n}","tryCatchPattern":"try {\n  await manager.callTool(toolName, args);\n} catch (e) {\n  if (e instanceof ChromaUnavailableError && e.message.includes('after shutdown begins')) {\n    pendingMutations.push({ toolName, args }); // defer to next session\n  } else { throw e; }\n}","preventionTips":["Await all sync/backfill work before calling shutdown on the manager","Queue mutations through enqueueMutation during normal operation so they are deferred, not rejected","Check manager lifecycle state before issuing write calls","Treat ChromaUnavailableError as deferral, persist and replay mutations later"],"tags":["chroma","shutdown","mutation","vector-store","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}