MemPalace/mempalace · error · ValueError

since_event_id {since_event_id!r} not found

Error message

since_event_id {since_event_id!r} not found

What it means

list_events was called with since_event_id set to an id that does not exist in the events table. That parameter is an exclusive cursor anchor: the rowid of the named event becomes the boundary ('rowid > ?') for pagination, so an unknown id has an unresolvable position and the call refuses rather than silently returning everything or nothing.

Source

Thrown at mempalace/logstream.py:742

        ):
            if value is not None:
                where.append(f"{column} = ?")
                params.append(value)
        if to_agent is not None:
            where.append("(to_agent = ? OR to_agent = '*')")
            params.append(to_agent)
        if since_created_at is not None:
            where.append("created_at >= ?")
            params.append(since_created_at)

        with self._lock:
            conn = self._conn()
            if since_event_id is not None:
                anchor = conn.execute(
                    "SELECT rowid FROM events WHERE id = ?", (since_event_id,)
                ).fetchone()
                if anchor is None:
                    raise ValueError(f"since_event_id {since_event_id!r} not found")
                where.append("rowid > ?")
                params.append(anchor["rowid"])

            sql = "SELECT rowid, * FROM events"
            if where:
                sql += " WHERE " + " AND ".join(where)
            sql += " ORDER BY rowid ASC LIMIT ?"
            params.append(limit)
            rows = conn.execute(sql, params).fetchall()
            events = [self._event_dict(row) for row in rows]
            return self._attach_artifact_ids(conn, events)

    def latest_event_id(self) -> Optional[str]:
        """Id of the newest event, or None on an empty log.

        Live-tail consumers (the SSE stream) capture this at connect time
        as their starting cursor so they receive only post-connect events.
        """

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Only use ids returned in the same database's earlier response (each event dict's 'id' field).
  2. After a reset or when unsure, re-baseline: fetch the newest events without since_event_id and re-anchor from the last row.
  3. Cross-check with ls.get_event(since_event_id) before paginating; treat not-found as 'restart from head'.

Example fix

// before
page = ls.list_events(since_event_id=anchor_id)  # anchor from a pre-reset db
// after
if ls.get_event(anchor_id) is None:
    anchor_id = None  # re-baseline from head
page = ls.list_events(since_event_id=anchor_id, limit=50)
if page:
    anchor_id = page[-1]["id"]
Defensive patterns

Strategy: fallback

Validate before calling

def valid_anchor(ls, event_id) -> bool:
    try:
        return ls.get_event(event_id) is not None
    except ValueError:
        return False

if not valid_anchor(ls, anchor_id):
    anchor_id = None  # re-baseline: read from head

Try / catch

try:
    page = ls.list_events(since_event_id=anchor_id, limit=50)
except ValueError as e:
    if "not found" in str(e):
        anchor_id = None  # stale anchor after reset/migration: start over from head
        page = ls.list_events(limit=50)
    else:
        raise

Prevention

When it happens

Trigger: list_events(since_event_id='evt_typo') after a copy error; paginating against a different db_path than the one the earlier page came from; using an id returned by an older query after the database was recreated; passing an artifact id ('art_...') instead of an event id.

Common situations: Long-running pollers that cache an anchor id across palace resets or db migrations; multi-replica reads where local replication lags the anchor's origin; consumers mixing up ids from artifacts and events.

Related errors


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