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
- Only use ids returned in the same database's earlier response (each event dict's 'id' field).
- After a reset or when unsure, re-baseline: fetch the newest events without since_event_id and re-anchor from the last row.
- 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
- Persist pagination cursors alongside the db identity (path + creation time) and invalidate on mismatch.
- After any db recreate or migration, drop cached anchors and re-baseline from head.
- Never feed artifact ids ('art_...') into since_event_id — it only accepts event ids.
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
- event {event_id!r} not found
- limit must be a positive integer
- after_seq must be a non-negative integer
- unknown job id: {job_id}
- {field_name} must be a non-empty string
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/ab8679ababfaf391.
Report an issue: GitHub.