MemPalace/mempalace · error · ValueError

since_event_id 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.

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.

Appendix: 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)