Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions mempalace/palace_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ def build_graph(col=None, config=None):
while offset < total:
batch = col.get(limit=1000, offset=offset, include=["metadatas"])
for meta in batch["metadatas"]:
# ChromaDB can return ``None`` for drawers without metadata
# (legacy data, partial writes — upstream #1020 territory).
# Skip these silently rather than crash the whole graph
# build — a single None drawer shouldn't take down /stats
# or any caller of build_graph for the entire palace. Caught
# 2026-04-25 by palace-daemon's verify-routes.sh smoke test
# against the canonical 151K palace. Closes the same gap as
# upstream #999 / fork PR #1094 in a different read path.
if meta is None:
continue
room = meta.get("room", "")
wing = meta.get("wing", "")
hall = meta.get("hall", "")
Expand Down
21 changes: 21 additions & 0 deletions tests/test_palace_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ def test_falsy_collection(self):
assert nodes == {}
assert edges == []

def test_none_metadata_does_not_crash(self):
"""ChromaDB can return None for drawers without metadata (legacy
data, partial writes — upstream #1020 territory). build_graph
must skip None entries silently rather than crash the whole
graph build with AttributeError. Caught 2026-04-25 by
palace-daemon's verify-routes.sh smoke test against the
canonical 151K palace; /stats was 500-ing on a single None
drawer and taking out every consumer of build_graph for the
whole call path."""
col = _make_fake_collection(
[
{"room": "auth", "wing": "wing_code", "hall": "security", "date": "2026-01-01"},
None, # legacy / partial-write drawer with no metadata
{"room": "auth", "wing": "wing_code", "hall": "security", "date": "2026-01-02"},
]
)
nodes, edges = build_graph(col=col)
# The two real drawers were processed; the None one was skipped.
assert "auth" in nodes
assert nodes["auth"]["count"] == 2

def test_single_wing_no_edges(self):
col = _make_fake_collection(
[
Expand Down
Loading