Skip to content

fix: guard None metadata/doc in tool_check_duplicate and Layer1/Layer2#1030

Merged
igorls merged 1 commit intoMemPalace:developfrom
eldar702:fix/none-metadata-residual-guards
May 6, 2026
Merged

fix: guard None metadata/doc in tool_check_duplicate and Layer1/Layer2#1030
igorls merged 1 commit intoMemPalace:developfrom
eldar702:fix/none-metadata-residual-guards

Conversation

@eldar702
Copy link
Copy Markdown
Contributor

Summary

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 #999, #1013 and in
flight #1019), but three call sites are still unguarded and reproduce
the same AttributeError: 'NoneType' object has no attribute 'get'
crash family:

1. mcp_server.tool_check_duplicatemcp_server.py:487-488

```python
meta = results["metadatas"][0][i] # can be None
doc = results["documents"][0][i] # can be None
...
"wing": meta.get("wing", "?"), # AttributeError
```

The broad except Exception wrapper (line 504) swallows the real
cause and returns an uninformative {\"error\": \"Duplicate check failed\"} to the MCP client.

2. layers.Layer1.generatelayers.py:126

```python
for doc, meta in zip(docs, metas):
...
for key in ("importance", "emotional_weight", "weight"):
val = meta.get(key) # AttributeError on None
```

A single None metadata blows up the whole L1_essential wake-up
render.

3. layers.Layer2.retrievelayers.py:224

Same pattern, same crash path, for the on-demand render.

Change

Apply the same meta = meta or {} / doc = doc or \"\" idiom the
merged guards in searcher.py already use. Three-line additions, no
behaviour change on well-formed results.

Test plan

Test What it covers
test_check_duplicate_handles_none_metadata Mocks col.query to return None at one metadata + one document slot; asserts the call does not crash and the sentinel-rendered entry has wing/room == \"?\" and empty content.
test_layer1_handles_none_metadata Layer1.generate() over a docs/metas pair where one metadata is None; asserts render succeeds and contains the well-formed memory.
test_layer1_handles_none_document Same but the document is None.
test_layer2_handles_none_metadata Layer2.retrieve() over a list with a None metadata; asserts L2 render emits the header without crashing.

All four new tests pass; full test_layers.py and test_mcp_server.py::TestWriteTools
suites pass unchanged.

Relationship to other open PRs

Why it doesn't introduce new issues

The or {} / or \"\" pattern is exactly what the existing
merged None-guard PRs used. It is a strict subset of behaviour change:
on well-formed results the coercion is a no-op; on None it
substitutes an empty sentinel that downstream .get(...) and
string-slicing tolerate. Crashes on these paths become graceful
renders — no data is silently lost because the underlying doc
content is already missing at the backend level.

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**.
@igorls igorls added bug Something isn't working area/mcp MCP server and tools labels Apr 24, 2026
@igorls igorls added this to the v3.3.5 milestone May 2, 2026
@igorls igorls merged commit 67cda9d into MemPalace:develop May 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/mcp MCP server and tools bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants