can1357/oh-my-pi · warning
RPC message pagination repeated a cursor
Error message
RPC message pagination repeated a cursor
What it means
While walking message pages, the client records every cursor it has seen; if the server ever returns a nextCursor it already returned, the loop would be infinite, so it throws. This is server-side pagination loop detection.
Source
Thrown at packages/coding-agent/src/modes/rpc/rpc-client.ts:887
async getMessages(): Promise<AgentMessage[]> {
if (this.#protocolVersion === 2) {
try {
const messages: AgentMessage[] = [];
const seenCursors = new Set<string>();
let totalMessages: number | undefined;
let cursor: string | undefined;
do {
const page = await this.getMessagesPage({ cursor, limit: 256 });
if (
!Number.isSafeInteger(page.totalMessages) ||
page.totalMessages < 0 ||
(totalMessages !== undefined && page.totalMessages !== totalMessages)
)
throw new Error("RPC message pagination returned an inconsistent total");
totalMessages = page.totalMessages;
messages.push(...page.messages);
cursor = page.nextCursor;
if (cursor && seenCursors.has(cursor)) throw new Error("RPC message pagination repeated a cursor");
if (cursor) seenCursors.add(cursor);
} while (cursor);
if (messages.length !== totalMessages)
throw new Error("RPC message pagination ended before the advertised total");
return messages;
} catch (error) {
if (!isPageFallbackError(error)) throw error;
}
}
const response = await this.#send({ type: "get_messages" });
return this.#getData<{ messages: AgentMessage[] }>(response).messages;
}
/**
* Get list of OAuth providers available for login, with their current authentication status.
*/
async getLoginProviders(): Promise<Array<{ id: string; name: string; available: boolean; authenticated: boolean }>> {
const response = await this.#send({ type: "get_login_providers" });View on GitHub (pinned to 9690622007)
Solutions
- Upgrade/fix the RPC server so nextCursor strictly advances (or is null when done).
- Retry pagination once message deletion/insertion has settled.
- Avoid mutating (deleting) messages during a history fetch.
- Use the non-paginated get_messages fallback path.
Example fix
// before const msgs = await client.getMessages(); // while jobs prune old messages // after await pruningComplete; const msgs = await client.getMessages();
Defensive patterns
Strategy: try-catch
Try / catch
try {
const msgs = await client.getMessages();
} catch (err) {
if ((err as Error).message.includes("repeated a cursor")) {
logger.error("server pagination loop — upgrade the RPC server");
} else throw err;
} Prevention
- Don't delete messages while paginating history.
- Use a conforming server whose nextCursor strictly advances.
- Report cursor loops as server bugs.
- Bound your own wait time so a loop cannot hang the app.
When it happens
Trigger: getMessagesPage returns a nextCursor equal to one previously returned — a buggy cursor implementation or a cursor that does not advance past already-fetched pages.
Common situations: An RPC server that regenerates the same cursor for the tail page; deleting messages mid-pagination so the cursor window doesn't advance; a non-conforming custom RPC host.
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/8f65fb53afecb402.
Report an issue: GitHub.