diff --git a/mempalace/searcher.py b/mempalace/searcher.py index 2722ed9..a38cad1 100644 --- a/mempalace/searcher.py +++ b/mempalace/searcher.py @@ -498,7 +498,16 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r # collection.count(); both happen before the old query-only guard and can # hit the same native crash. Non-Chroma backends never use Chroma's HNSW # files or sqlite-specific fallback and proceed normally. - if resolve_backend_name(palace_path) == "chroma" and _hnsw_capacity_diverged(palace_path): + try: + backend_name = resolve_backend_name(palace_path) + except (BackendMismatchError, KeyError): + # Preserve _open_collection_or_explain's state-specific diagnostics + # for mixed artifacts and unknown backend selections. This probe is + # only an early Chroma safety fence; it must not become a second, + # less-helpful backend validation path. + backend_name = None + + if backend_name == "chroma" and _hnsw_capacity_diverged(palace_path): return _print_search_results_bm25_only(query, palace_path, wing, room, n_results) col = _open_collection_or_explain(palace_path, opener=get_collection) diff --git a/tests/test_searcher.py b/tests/test_searcher.py index 9dba4e4..c2fe5b9 100644 --- a/tests/test_searcher.py +++ b/tests/test_searcher.py @@ -11,7 +11,14 @@ import pytest from _chroma_palace_helper import make_minimal_chroma_sqlite -from mempalace.searcher import SearchError, build_where_filter, search, search_memories +from mempalace.backends import BackendMismatchError +from mempalace.searcher import ( + SearchError, + build_where_filter, + get_collection, + search, + search_memories, +) # ── build_where_filter (unit) ────────────────────────────────────────── @@ -616,3 +623,22 @@ class TestSearchCLI: mock_probe.assert_not_called() mock_col.query.assert_called_once() assert "backend-native result" in capsys.readouterr().out + + @pytest.mark.parametrize( + "resolution_error", + [BackendMismatchError("mixed backend artifacts"), KeyError("unknown_backend")], + ) + def test_search_delegates_backend_resolution_errors_to_open_diagnostic( + self, fake_palace_path, resolution_error + ): + """The early HNSW fence must not replace normal CLI diagnostics.""" + with ( + patch("mempalace.searcher.resolve_backend_name", side_effect=resolution_error), + patch("mempalace.searcher._hnsw_capacity_diverged") as mock_probe, + patch("mempalace.searcher._open_collection_or_explain", return_value=None) as mock_open, + ): + with pytest.raises(SearchError): + search("anything", fake_palace_path) + + mock_probe.assert_not_called() + mock_open.assert_called_once_with(fake_palace_path, opener=get_collection)