MemPalace/mempalace · error · ValueError
metadata is not JSON-serializable: {exc}
Error message
metadata is not JSON-serializable: {exc} What it means
The metadata dict could not be serialized with json.dumps (TypeError/ValueError), meaning it contains values JSON cannot represent — sets, custom objects, bytes, non-string dict keys, or NaN/Infinity with strict encoders. The original exception text is appended so the offending value is identifiable. This runs after the dict type check.
Source
Thrown at mempalace/logstream.py:165
if "\x00" in value:
raise ValueError(f"{field_name} contains null bytes")
value = strip_lone_surrogates(value)
size = len(value.encode("utf-8"))
if size > max_bytes:
raise ValueError(f"{field_name} is {size} bytes; maximum is {max_bytes} bytes")
return value
def _sanitize_metadata(value) -> str:
"""Validate optional metadata dict and return its canonical JSON text."""
if value is None:
return "{}"
if not isinstance(value, dict):
raise ValueError("metadata must be an object")
try:
encoded = json.dumps(value, ensure_ascii=False, sort_keys=True)
except (TypeError, ValueError) as exc:
raise ValueError(f"metadata is not JSON-serializable: {exc}") from None
if len(encoded.encode("utf-8")) > MAX_METADATA_BYTES:
raise ValueError(f"metadata exceeds maximum size of {MAX_METADATA_BYTES} bytes")
return encoded
class Logstream:
"""Durable append-only coordination log (events + artifacts).
Storage and threading mirror ``KnowledgeGraph``: one SQLite file in
WAL mode, a per-instance lock around writes, ``check_same_thread=False``
so the MCP HTTP server can call from worker threads.
"""
def __init__(
self,
db_path: str,
max_body_bytes: int = DEFAULT_MAX_BODY_BYTES,
max_artifact_bytes: int = DEFAULT_MAX_ARTIFACT_BYTES,View on GitHub (pinned to 06cb6987f0)
Solutions
- Convert non-native values before the call: datetime.isoformat(), list(set), bytes.decode(), str(Path).
- Use a default hook by pre-serializing: json.dumps(metadata, default=str) then json.loads the result before passing.
- Replace unserializable entries with plain str/int/float/bool/None/list/dict.
Example fix
// before
ls.append_event(..., metadata={"at": datetime.now(), "tags": {"a"}})
// after
ls.append_event(..., metadata={"at": datetime.now(timezone.utc).isoformat(), "tags": ["a"]}) Defensive patterns
Strategy: validation
Validate before calling
def json_safe_metadata(md: dict) -> dict:
return json.loads(json.dumps(md, ensure_ascii=False, default=str))
metadata = json_safe_metadata(metadata) # datetime/set/Path become JSON-native Type guard
def is_json_serializable(md) -> bool:
if not isinstance(md, dict):
return False
try:
json.dumps(md, ensure_ascii=False)
return True
except (TypeError, ValueError):
return False Try / catch
try:
evt = ls.append_event(..., metadata=metadata)
except ValueError as e:
if "not JSON-serializable" in str(e):
evt = ls.append_event(..., metadata=json.loads(json.dumps(metadata, default=str)))
else:
raise Prevention
- Convert datetime -> isoformat, set -> sorted list, Path/UUID -> str before building the dict.
- Run is_json_serializable in unit tests over every metadata shape your producer emits.
When it happens
Trigger: append_event(metadata={'ids': {1, 2}}) (set); metadata={'when': datetime.now()} (datetime); metadata={'blob': b'x'} (bytes); metadata={(1,2): 'v'} (tuple key); nested dataclass instances.
Common situations: Reusing ORM/model objects as metadata; mixing stdlib types (Path, datetime, set) that are convenient in Python but not JSON-native; cache values from third-party libs leaking into annotations.
Related errors
- --metadata must be a JSON object
- metadata must be an object
- metadata exceeds maximum size of {MAX_METADATA_BYTES} bytes
- operator {key!r} not supported by chroma backend
- metadata key {key!r} clashes with a reserved Milvus field
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/925d9ddf67a10261.
Report an issue: GitHub.