Skip to content

Commit 28f6cd7

Browse files
jpheinclaude
authored andcommitted
fix(palace_graph): skip None metadata in build_graph
ChromaDB can return None for drawers without metadata (legacy data, partial writes — same root cause as upstream MemPalace#1020 / our PR MemPalace#1094). build_graph at line 95 called meta.get("room", "") unconditionally, which AttributeErrors on None and takes out every consumer of build_graph for the whole call path: graph_stats, find_tunnels, traverse, and (most visibly) the daemon's /stats endpoint. Caught 2026-04-25 by palace-daemon's verify-routes.sh smoke test against the canonical 151K-drawer palace — /stats was 500-ing on a single None drawer. Adds `if meta is None: continue` guard. Closes the same gap upstream's MemPalace#999 None-metadata audit closed in searcher.py / mcp_server.py / miner.status, just in a different file the audit didn't reach. The graph-build is recoverable: skipping a single None drawer doesn't distort the graph since build_graph already filters `room and room != "general" and wing` — a missing-metadata drawer was never going to participate anyway. Test: TestBuildGraph::test_none_metadata_does_not_crash mixes a None entry into a 3-drawer fixture and asserts the two real drawers are processed normally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent da11bea commit 28f6cd7

2 files changed

Lines changed: 31 additions & 0 deletions

File tree

mempalace/palace_graph.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ def build_graph(col=None, config=None):
9292
while offset < total:
9393
batch = col.get(limit=1000, offset=offset, include=["metadatas"])
9494
for meta in batch["metadatas"]:
95+
# ChromaDB can return ``None`` for drawers without metadata
96+
# (legacy data, partial writes — upstream #1020 territory).
97+
# Skip these silently rather than crash the whole graph
98+
# build — a single None drawer shouldn't take down /stats
99+
# or any caller of build_graph for the entire palace. Caught
100+
# 2026-04-25 by palace-daemon's verify-routes.sh smoke test
101+
# against the canonical 151K palace. Closes the same gap as
102+
# upstream #999 / fork PR #1094 in a different read path.
103+
if meta is None:
104+
continue
95105
room = meta.get("room", "")
96106
wing = meta.get("wing", "")
97107
hall = meta.get("hall", "")

tests/test_palace_graph.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ def test_falsy_collection(self):
5454
assert nodes == {}
5555
assert edges == []
5656

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

0 commit comments

Comments
 (0)