{"record":{"id":"2d26c4d580f5d262","repo":"MemPalace/mempalace","slug":"chromabackend-has-been-closed","errorCode":null,"errorMessage":"ChromaBackend has been closed","messagePattern":"ChromaBackend has been closed","errorType":"exception","errorClass":"BackendClosedError","httpStatus":null,"severity":"error","filePath":"mempalace/backends/chroma.py","lineNumber":2285,"sourceCode":"\n        Handles the palace-rebuild case (repair/nuke/purge) by invalidating the\n        cache when ``chroma.sqlite3`` changes on disk. Mirrors the semantics of\n        ``mcp_server._get_client`` (merged via #757):\n\n        * DB file missing while we hold a cached client → drop the cache so we\n          do not serve stale data after a rebuild that has not yet re-created\n          the DB.\n        * Transition 0 → nonzero stat (DB created after cache) counts as a\n          change, so the cached client is replaced with one that sees the DB.\n        * FAT/exFAT filesystems return inode 0; we never fire inode comparisons\n          when either side is 0 (safe fallback) but still honor mtime.\n        * Mtime change uses an epsilon (0.01 s) to tolerate FS timestamp\n          granularity without thrashing.\n        \"\"\"\n        if self._closed:\n            from .base import BackendClosedError  # late import avoids cycles at module load\n\n            raise BackendClosedError(\"ChromaBackend has been closed\")\n\n        cached = self._clients.get(palace_path)\n        cached_inode, cached_mtime = self._freshness.get(palace_path, (0, 0.0))\n        current_inode, current_mtime = self._db_stat(palace_path)\n\n        db_path = os.path.join(palace_path, \"chroma.sqlite3\")\n        # DB was present when cache was built but is now missing → invalidate.\n        if cached is not None and not os.path.isfile(db_path):\n            _close_client(self._clients.pop(palace_path, None))\n            self._freshness.pop(palace_path, None)\n            cached = None\n            cached_inode, cached_mtime = 0, 0.0\n\n        inode_changed = current_inode != 0 and cached_inode != 0 and current_inode != cached_inode\n        # Transition from no-stat (0.0) to a real stat counts as a change so we\n        # pick up a DB that was created after the cache was built.\n        mtime_appeared = cached_mtime == 0.0 and current_mtime != 0.0\n        mtime_changed = (","sourceCodeStart":2267,"sourceCodeEnd":2303,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/backends/chroma.py#L2267-L2303","documentation":"A ValueError from _validate_candidate_strategy: search_memories was called with a candidate_strategy that is not a key of _CANDIDATE_MERGERS. Validation happens eagerly at the top of search_memories so the same error fires regardless of whether the call takes the vector path, BM25 fallback, or an early-error return — callers get consistent, immediate feedback instead of a downstream KeyError.","triggerScenarios":"Passing candidate_strategy='interleave' (or any typo/unimplemented name) to search_memories; strategy not among the registered merger names.","commonSituations":"Typos in config files (mempalace.yaml search settings), code written against an older/newer version whose strategy names differ, or dynamically-built strategy strings.","solutions":["Check the message: it lists the valid strategies as tuple(_CANDIDATE_MERGERS) — pick one of those.","If the value comes from config, fix the config key (commonly a typo like 'union' vs 'merge').","If you need a custom strategy, register a merger in _CANDIDATE_MERGERS rather than passing an unknown name."],"exampleFix":"# before\nsearch_memories(q, candidate_strategy='merge-all')\n\n# after\nsearch_memories(q, candidate_strategy='union')  # a registered name","handlingStrategy":"validation","validationCode":"from mempalace.searcher import _CANDIDATE_MERGERS\nif strategy not in _CANDIDATE_MERGERS:\n    raise SystemExit(f'bad strategy {strategy!r}; valid: {sorted(_CANDIDATE_MERGERS)}')","typeGuard":"def is_valid_candidate_strategy(s: str) -> bool:\n    from mempalace.searcher import _CANDIDATE_MERGERS\n    return s in _CANDIDATE_MERGERS","tryCatchPattern":null,"preventionTips":["Validate config-supplied strategy names against available keys before calling search.","Pin the mempalace version so strategy names in config match the installed merger registry."],"tags":["search","validation","config","typo"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}