MemPalace/mempalace · error · ValueError
limit must be a positive integer
Error message
limit must be a positive integer
What it means
list_events (and wait_events, which forwards its filters) requires `limit` to be an int >= 1. Zero, negative numbers, floats like 10.0, and None all fail. Valid limits are then clamped to MAX_LIST_LIMIT = 500, so any positive integer works but is capped. bool is an int subclass, so True passes as limit=1 — usually a latent bug rather than intent.
Source
Thrown at mempalace/logstream.py:712
event in append order (rowid), regardless of timestamp ties.
- ``since_created_at`` is inclusive (``>=``) so second-granularity
timestamps never skip events; callers dedup by ``id``.
- ``to_agent`` also matches broadcast events (``to_agent='*'``).
"""
stream = _sanitize_routing(stream, "stream", required=False)
room = _sanitize_routing(room, "room", required=False)
if type not in (None, ""):
type = _sanitize_event_type(type)
else:
type = None
to_agent = _sanitize_routing(to_agent, "to_agent", required=False)
from_agent = _sanitize_routing(from_agent, "from_agent", required=False)
correlation_id = _sanitize_routing(correlation_id, "correlation_id", required=False)
status = _sanitize_status(status)
since_event_id = _sanitize_routing(since_event_id, "since_event_id", required=False)
since_created_at = sanitize_iso_temporal(since_created_at, "since_created_at") or None
if not isinstance(limit, int) or limit < 1:
raise ValueError("limit must be a positive integer")
limit = min(limit, MAX_LIST_LIMIT)
where = []
params = []
for column, value in (
("stream", stream),
("room", room),
("type", type),
("from_agent", from_agent),
("correlation_id", correlation_id),
("status", status),
):
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)View on GitHub (pinned to 06cb6987f0)
Solutions
- Pass a positive int: limit=50 (the DEFAULT_LIST_LIMIT) or anything 1..500.
- For 'as much as possible' use limit=500 (MAX_LIST_LIMIT) and paginate with since_event_id.
- Guard computed values: limit=max(1, min(int(limit or 50), 500)).
Example fix
// before evts = ls.list_events(stream="project/x", limit=len(rooms)) # 0 when empty // after limit = max(1, min(len(rooms) or 50, 500)) evts = ls.list_events(stream="project/x", limit=limit)
Defensive patterns
Strategy: validation
Validate before calling
def safe_limit(limit, default=50, cap=500):
if limit is None:
return default
return max(1, min(int(limit), cap))
limit = safe_limit(raw_limit) Type guard
def is_valid_limit(v) -> bool:
return isinstance(v, int) and not isinstance(v, bool) and v >= 1 Try / catch
try:
evts = ls.list_events(stream=s, limit=limit)
except ValueError as e:
if "limit must be a positive integer" in str(e):
evts = ls.list_events(stream=s, limit=50)
else:
raise Prevention
- Never use 0 or -1 as 'all' — cap at MAX_LIST_LIMIT (500) and paginate with since_event_id.
- Coerce config floats to int at load time.
- Exclude bool from limit handling; it silently passes as 1.
When it happens
Trigger: list_events(limit=0); limit=-1 to mean 'all'; limit=None when no default applied; limit=50.0 from a config float; limit computed as len(items) when the list is empty.
Common situations: Config files parsed with float values; callers using 0 as a sentinel for 'no limit'; a computed limit that can be 0 for empty result sets; JSON input where the field is optional and arrives as null.
Related errors
- {field_name} must be a non-empty string
- {field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH}
- {field_name} contains control characters
- type must be a non-empty string
- type={value!r} is not a valid event type (lowercase letters,
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/33a936c30f041256.
Report an issue: GitHub.