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

  1. Always pass a list of strings: artifact_ids=[art['id']].
  2. Coerce before calling: artifact_ids=[str(a) for a in ids if a].
  3. 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

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


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/61d42794e5328b6f. Report an issue: GitHub.