MemPalace/mempalace · error · ValueError
artifact_ids must be a list of artifact id strings
Error message
artifact_ids must be a list of artifact id strings
What it means
The artifact_ids argument of append_event must be a list (or None) whose elements are all non-empty strings. Anything else — a single id string, a tuple, a set, a list with ints or empty strings — is rejected before any database lookup. ids must already exist as artifacts (see the follow-up unknown-artifact error for semantic failures).
Source
Thrown at mempalace/logstream.py:424
"""
type = _sanitize_event_type(type)
stream = _sanitize_routing(stream, "stream")
room = _sanitize_routing(room, "room")
from_agent = _sanitize_routing(from_agent, "from_agent")
to_agent = _sanitize_routing(to_agent, "to_agent", required=False)
correlation_id = _sanitize_routing(correlation_id, "correlation_id", required=False)
branch = _sanitize_routing(branch, "branch", required=False)
base_commit = _sanitize_routing(base_commit, "base_commit", required=False)
status = _sanitize_status(status)
body = _sanitize_body(body, self.max_body_bytes)
metadata_json = _sanitize_metadata(metadata)
if artifact_ids is None:
artifact_ids = []
if not isinstance(artifact_ids, list) or not all(
isinstance(a, str) and a for a in artifact_ids
):
raise ValueError("artifact_ids must be a list of artifact id strings")
artifact_ids = list(dict.fromkeys(artifact_ids)) # dedup, keep order
event_id = _new_id("evt")
created_at = _utc_now_iso()
hlc = self._clock.tick()
with self._lock:
conn = self._conn()
with conn:
for artifact_id in artifact_ids:
found = conn.execute(
"SELECT 1 FROM artifacts WHERE id = ?", (artifact_id,)
).fetchone()
if not found:
raise ValueError(
f"artifact_ids references unknown artifact {artifact_id!r}"
)
cursor = conn.execute(View on GitHub (pinned to 06cb6987f0)
Solutions
- Always pass a list of strings: artifact_ids=[art['id']].
- Coerce before calling: artifact_ids=[str(a) for a in ids if a].
- Pass artifact_ids=None (or omit) when the event references no artifacts — it defaults to [].
Example fix
// before evt = ls.append_event(..., artifact_ids=art["id"]) // after evt = ls.append_event(..., artifact_ids=[art["id"]])
Defensive patterns
Strategy: type-guard
Validate before calling
def as_artifact_ids(value):
if value is None:
return []
if isinstance(value, str):
value = [value]
if not isinstance(value, (list, tuple)):
raise TypeError("artifact_ids must be a list of id strings")
return [str(a) for a in value if a]
artifact_ids = as_artifact_ids(raw_ids) Type guard
def is_valid_artifact_ids(v) -> bool:
return v is None or (isinstance(v, list) and all(isinstance(a, str) and a for a in v)) Try / catch
try:
evt = ls.append_event(..., artifact_ids=artifact_ids)
except ValueError as e:
if "must be a list" in str(e):
evt = ls.append_event(..., artifact_ids=[artifact_ids])
else:
raise Prevention
- Always wrap single ids in brackets at the call site.
- Use the ids exactly as returned by put_artifact — never reconstruct or truncate them.
When it happens
Trigger: append_event(artifact_ids='art_20250814_ab12') (bare string, not a list); artifact_ids=('a','b') (tuple); artifact_ids=[123]; artifact_ids=['art1', ''] (empty element); artifact_ids={'a','b'} (set).
Common situations: Callers with a single artifact forgetting the brackets; code paths that build ids from int rowids; JSON deserialization yielding a list of numbers; defensive conversion from other collections left as generator/tuple.
Related errors
- {field_name} must be a string
- metadata must be an object
- {field_name} must be a non-empty string
- {field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH}
- {field_name} contains control characters
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/61d42794e5328b6f.
Report an issue: GitHub.