From 49e9e04a129f7dd42f6f076d144cc528a0ff9515 Mon Sep 17 00:00:00 2001 From: bensig <1872138+bensig@users.noreply.github.com> Date: Sat, 18 Apr 2026 11:41:35 -0700 Subject: [PATCH] fix: guard Layer3.search_raw against None doc/meta from ChromaDB (#1011) Same class of bug as #1007: ChromaDB's query() can return None in the documents and metadatas arrays when a drawer's HNSW vector entry exists but its metadata/document rows haven't been materialized. The code in Layer3.search_raw (mempalace/layers.py) calls meta.get("wing", ...), meta.get("room", ...), meta.get("source_file", ...) directly without null safety, so it raises: AttributeError: 'NoneType' object has no attribute 'get' Two-line defensive coercion matching the pattern in #1009 / PR #999 for searcher.py: meta = meta or {}, doc = doc or "". The hit still appears with its real distance; source/wing/room fall back to their fallback values where the metadata row is missing. Frequently hit on chromadb 1.5.x (root cause #1006). Even after the chromadb floor lands (#1010), partial-state results remain possible during interrupted mines and schema upgrade boundaries, so the guard is worth having on its own. Fixes #1011. --- mempalace/layers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mempalace/layers.py b/mempalace/layers.py index ff2ff1c..a0f9b6d 100644 --- a/mempalace/layers.py +++ b/mempalace/layers.py @@ -281,6 +281,8 @@ class Layer3: lines = [f'## L3 — SEARCH RESULTS for "{query}"'] for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1): + meta = meta or {} + doc = doc or "" similarity = round(1 - dist, 3) wing_name = meta.get("wing", "?") room_name = meta.get("room", "?") @@ -327,6 +329,13 @@ class Layer3: _first_or_empty(results, "metadatas"), _first_or_empty(results, "distances"), ): + # ChromaDB may return None for doc/meta when a drawer's HNSW entry + # exists but its metadata/document rows haven't been materialized + # (partial-flush states, mid-delete, schema upgrade boundaries). + # Degrade gracefully — the hit still appears with real distance; + # storage fields show their fallback where content is missing. + meta = meta or {} + doc = doc or "" hits.append( { "text": doc,