can1357/oh-my-pi · error · RpcError
RPC message pagination repeated a cursor
Error message
RPC message pagination repeated a cursor
What it means
The pagination loop tracks seen_cursors to detect non-converging iteration. If get_messages_page returns a next_cursor that was already visited, the loop would otherwise spin forever, so the client raises this RpcError. It indicates the server's cursor is not advancing (or is cycling) between pages.
Source
Thrown at python/omp-rpc/src/omp_rpc/client.py:1044
seen_cursors: set[str] = set()
total_messages: int | None = None
cursor: str | None = None
while True:
page = self.get_messages_page(cursor=cursor, limit=256)
if (
total_messages is not None
and page.total_messages != total_messages
):
raise RpcError(
"RPC message pagination returned an inconsistent total"
)
total_messages = page.total_messages
messages.extend(page.messages)
cursor = page.next_cursor
if cursor is None:
break
if cursor in seen_cursors:
raise RpcError("RPC message pagination repeated a cursor")
seen_cursors.add(cursor)
if len(messages) != total_messages:
raise RpcError(
"RPC message pagination ended before the advertised total"
)
return tuple(messages)
except RpcCommandError as error:
if error.command != "get_messages_page" or not (
error.code in _RPC_MESSAGES_PAGE_FALLBACK_CODES
or error.error
in (
_RPC_MESSAGES_PAGE_BUSY_ERROR,
_RPC_MESSAGES_PAGE_STALE_ERROR,
)
):
raise
payload = self._request("get_messages")
return parse_agent_messages(cast(JsonValue | None, payload.get("messages")))View on GitHub (pinned to 9690622007)
Solutions
- Upgrade the omp server to a build with correct cursor advancement
- Retry full pagination once the session is idle (concurrent mutation can invalidate cursors)
- If testing, make the mock server advance the cursor monotonically and terminate with nextCursor=None
- Catch RpcError and fall back to the non-paginated messages fetch if one exists
Example fix
# before: stub server always returns the same cursor
return {"messages": page, "totalMessages": n, "nextCursor": "abc"} # loops forever
# after: advance or terminate the cursor
cursor = None if at_end else encode_offset(offset + len(page))
return {"messages": page, "totalMessages": n, "nextCursor": cursor} Defensive patterns
Strategy: retry
Validate before calling
# basic server sanity check before paginating page = client.get_messages_page(cursor=None, limit=1) # a healthy server must terminate or advance; mocks must never echo cursors
Type guard
def cursor_advances(page, seen: set) -> bool:
return page.next_cursor is None or page.next_cursor not in seen
Try / catch
try:
messages = client.get_all_messages()
except RpcError as e:
if "repeated a cursor" in str(e):
messages = retry_pagination_or_fallback() # restart once session idle
else:
raise
Prevention
- Keep server and client versions aligned (cursor bugs are usually server-side)
- Make test doubles advance cursors monotonically and end with nextCursor=None
- Don't mutate the session while paginating
- Bound retries so a persistently broken server fails fast
When it happens
Trigger: Server returns the same next_cursor for consecutive get_messages_page calls — e.g. a buggy or mocked get_messages_page, a cursor invalidated by concurrent message mutation, or a server version whose cursor encoding changed.
Common situations: Older server build with a broken pagination cursor; custom/stub server that always echoes the same cursor; session state rewritten mid-pagination so the cursor points back to an earlier offset.
Related errors
- RPC message pagination repeated a cursor
- Invalid RPC message cursor
- stale_cursor
- RPC message pagination returned an inconsistent total
- RPC message pagination ended before the advertised total
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e8f1ec83c121a102.
Report an issue: GitHub.