can1357/oh-my-pi · error · RpcError
get_messages_page response has an invalid nextCursor
Error message
get_messages_page response has an invalid nextCursor
What it means
In the same _parse_messages_page validation, nextCursor must be either absent/null or a string. Any other JSON type (number, object, bool) raises this RpcError. The cursor is echoed back verbatim on the next get_messages_page call, so a non-string cursor would fail downstream request construction.
Source
Thrown at python/omp-rpc/src/omp_rpc/client.py:1077
):
raise
payload = self._request("get_messages")
return parse_agent_messages(cast(JsonValue | None, payload.get("messages")))
def get_messages_page(
self, *, cursor: str | None = None, limit: int | None = None
) -> MessagesPage:
payload = self._request("get_messages_page", cursor=cursor, limit=limit)
raw_total = payload.get("totalMessages")
if (
not isinstance(raw_total, int)
or isinstance(raw_total, bool)
or raw_total < 0
):
raise RpcError("get_messages_page response has an invalid totalMessages")
raw_cursor = payload.get("nextCursor")
if raw_cursor is not None and not isinstance(raw_cursor, str):
raise RpcError("get_messages_page response has an invalid nextCursor")
return MessagesPage(
messages=parse_agent_messages(
cast(JsonValue | None, payload.get("messages"))
),
total_messages=raw_total,
next_cursor=raw_cursor,
)
def set_custom_tools(self, tools: Sequence[HostTool[Any, Any]]) -> tuple[str, ...]:
self._custom_tools = tuple(tools)
if self._process is None:
return tuple(tool.name for tool in self._custom_tools)
payload = self._request(
"set_host_tools",
tools=cast(
JsonValue,
[View on GitHub (pinned to 9690622007)
Solutions
- Upgrade server and client to matching versions so the cursor type agrees
- In mocks/stubs, emit nextCursor as a string (or omit it / use null at the end)
- Coerce the server-side cursor to a string before responding
- Log the raw payload to confirm the offending cursor type
Example fix
# before: numeric cursor
return {"messages": msgs, "totalMessages": 10, "nextCursor": 42}
# after: opaque string cursor
return {"messages": msgs, "totalMessages": 10, "nextCursor": str(offset)} Defensive patterns
Strategy: type-guard
Validate before calling
def valid_cursor(payload: dict) -> bool:
c = payload.get("nextCursor")
return c is None or isinstance(c, str)
Type guard
def is_valid_cursor(v: object) -> bool:
return v is None or isinstance(v, str)
Try / catch
try:
page = client.get_messages_page(cursor=cursor, limit=256)
except RpcError as e:
if "invalid nextCursor" in str(e):
page = None # treat as schema drift; stop paging or upgrade server
else:
raise
Prevention
- Emit cursors as opaque strings (or null at the end) in servers/mocks
- Never encode cursors as raw ints/objects; keep the wire schema stable
- Version-gate any cursor format change on both sides
- Add schema assertions to server tests for get_messages_page
When it happens
Trigger: Server (or mock) returns nextCursor as a number, dict, or other non-string value; server version that encodes cursors as ints while the client expects opaque strings.
Common situations: Version skew where the server changed cursor encoding; hand-written test doubles returning raw offsets; middleware re-serializing the payload.
Related errors
- set_model returned an empty payload
- get_messages_page response has an invalid totalMessages
- set_host_tools response did not include toolNames
- set_host_uri_schemes response did not include schemes
- RPC message pagination repeated a cursor
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/07e9d1864d21720a.
Report an issue: GitHub.