MemPalace/mempalace · error · ValueError
content must be a non-empty string
Error message
content must be a non-empty string
What it means
put_artifact requires `content` to be a non-empty str. Empty artifacts are meaningless (v1 stores exact UTF-8 text with a sha256 digest, and an empty payload has no content to verify), and None is not treated as ''. Binary or structured values are also rejected here since content must be text.
Source
Thrown at mempalace/logstream.py:526
kind: str,
content: str,
created_by: str,
metadata: dict = None,
) -> dict:
"""Store exact artifact content (v1: UTF-8 text only).
Returns the artifact record without echoing ``content`` back —
callers already hold the content; readers use :meth:`get_artifact`.
For ``kind=patch``, a ``warnings`` list is included when the diff
looks unappliable (missing trailing newline, CRLF endings); the
content itself is still stored verbatim.
"""
if not isinstance(kind, str) or kind not in ARTIFACT_KINDS:
allowed = ", ".join(sorted(ARTIFACT_KINDS))
raise ValueError(f"kind={kind!r} is not one of: {allowed}")
created_by = _sanitize_routing(created_by, "created_by")
if not isinstance(content, str) or not content:
raise ValueError("content must be a non-empty string")
if "\x00" in content:
raise ValueError("content contains null bytes")
content = strip_lone_surrogates(content)
raw = content.encode("utf-8")
if len(raw) > self.max_artifact_bytes:
raise ValueError(
f"content is {len(raw)} bytes; maximum is {self.max_artifact_bytes} bytes"
)
metadata_json = _sanitize_metadata(metadata)
artifact_id = _new_id("art")
created_at = _utc_now_iso()
digest = sha256(raw).hexdigest()
with self._lock:
conn = self._conn()
with conn:
conn.execute(View on GitHub (pinned to 06cb6987f0)
Solutions
- Skip the put_artifact call when content is falsy: if not content: return.
- If the payload is genuinely empty by design, store a sentinel note ('(empty)') or record the fact in the event body instead.
- Ensure text, not bytes: content=raw.decode('utf-8').
Example fix
// before
art = ls.put_artifact(kind="file", content=open(p).read(), ...) # empty file
// after
content = open(p).read()
if content:
art = ls.put_artifact(kind="file", content=content, ...)
else:
art = None # nothing to store Defensive patterns
Strategy: validation
Validate before calling
def storable_content(content) -> bool:
return isinstance(content, str) and bool(content)
if not storable_content(content):
return None # nothing to store; skip put_artifact entirely Type guard
def is_storable_text(content) -> bool:
return isinstance(content, str) and len(content) > 0 Try / catch
try:
art = ls.put_artifact(kind=kind, content=content, ...)
except ValueError as e:
if "non-empty string" in str(e):
return None # treat empty payload as no-op
raise Prevention
- Short-circuit on falsy content before calling; an empty artifact is usually a bug upstream.
- When rendering templates or diffs, assert non-empty output before storing.
When it happens
Trigger: put_artifact(kind='file', content='') after reading an empty file; content=None when a template render produced nothing; content=b'bytes'; content=[] from a failed subprocess capture.
Common situations: Reading a file that is empty or was deleted mid-run; a diff generator returning '' on no changes; subprocess.check_output errors swallowed and passed through as None.
Related errors
- kind={kind!r} is not one of: {allowed}
- content contains null bytes
- content is {len(raw)} bytes; maximum is {self.max_artifact_b
- query input must be a non-empty list
- {field_name} must be a non-empty string
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/b92ed1446330f021.
Report an issue: GitHub.