fix(searcher): guard against None metadata/doc in search result loops#1019
Merged
igorls merged 1 commit intoMemPalace:developfrom May 6, 2026
Conversation
eldar702
added a commit
to eldar702/mempalace
that referenced
this pull request
Apr 19, 2026
Chroma 1.5.x can return ``None`` inside the ``metadatas`` / ``documents`` lists of a query/get result for partially-flushed rows. The codebase already has a systemic None-guard pattern (merged MemPalace#999, MemPalace#1013, MemPalace#1019) but three call sites were still unguarded: * ``mcp_server.tool_check_duplicate`` (``mcp_server.py:487-488``) — ``meta = results["metadatas"][0][i]`` followed by ``meta.get(...)`` raises ``AttributeError: 'NoneType' object has no attribute 'get'``. The broad ``except Exception`` wrapper (line 504) swallows it and returns an uninformative ``"Duplicate check failed"``. * ``layers.Layer1.generate`` (``layers.py:126``) — iterates ``zip(docs, metas)`` and calls ``meta.get(key)`` in the importance loop. A single None metadata blows up the entire wake-up render. * ``layers.Layer2.retrieve`` (``layers.py:224``) — same pattern, same crash path for the on-demand render. Apply the same ``meta = meta or {}`` / ``doc = doc or ""`` idiom used by the merged guards in the search path. Three-line additions, no behaviour change on well-formed results. Tests added: * ``test_check_duplicate_handles_none_metadata`` — mocks the collection query to return ``None`` for one metadata and document, asserts the call does not crash and the sentinel-rendered entry has wing/room "?" and empty content. * ``test_layer1_handles_none_metadata`` / ``_handles_none_document`` * ``test_layer2_handles_none_metadata`` Relationship to other open PRs: * **MemPalace#1019** guarded ``searcher.py`` loops. This PR extends the same guard to the three call sites MemPalace#1019 did not touch. * **MemPalace#979** fixed ``tool_check_duplicate`` negative similarity but left the None-metadata path unguarded. * Does not overlap **MemPalace#1013** (``Layer3.search_raw``) or **MemPalace#999**.
ChromaDB can return None entries in metadatas/documents lists under
partial-flush, mid-delete, upgrade-boundary, and interrupted-mine
states. Add `meta = meta or {}` and `doc = doc or ""` guards in the
three result loops (search display, closet hybrid, drawer scored) so
.get() and .strip() calls never crash on None.
Fixes MemPalace#1007, MemPalace#1011
fd34a38 to
733e435
Compare
Member
|
Rebased onto develop (post-#1029 + #1030 hybrid-rank refactor and clamp). Conflict in
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
mempalace searchcrashes withAttributeError: 'NoneType' object has no attribute 'get'when ChromaDB returnsNonefor a metadata or document entry. This happens in several edge-case states:embedding_metadatahasn't materialized yetFixes #1007, #1011
Fix
Add
meta = meta or {}anddoc = doc or ""defensive guards at the top of the three result-iteration loops insearcher.py:search()display loop (line ~284) — guardsmeta.get()anddoc.strip()callssearch_memories()closet hybrid loop (line ~368) — guardscmeta.get()search_memories()drawer scored loop (line ~388) — guardsmeta.get()Results with
Nonemetadata degrade gracefully (showing?placeholders) rather than crashing.Testing
The fix is a two-line defensive guard per loop — verifiable by mocking a ChromaDB query response with
{"documents": [[None]], "metadatas": [[None]], "distances": [[0.5]], "ids": [["x"]]}and confirmingsearch()completes without raising.