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

  1. Upgrade/fix the RPC server so nextCursor strictly advances (or is null when done).
  2. Retry pagination once message deletion/insertion has settled.
  3. Avoid mutating (deleting) messages during a history fetch.
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/8f65fb53afecb402. Report an issue: GitHub.