can1357/oh-my-pi · error

Invalid RPC message cursor

Error message

Invalid RPC message cursor

What it means

decodeCursor validates pagination cursors before decoding: it must be non-empty, at most 2048 chars, and match base64url alphabet [A-Za-z0-9_-]. This throw means the cursor string is empty, too long, or contains characters outside the base64url alphabet (e.g. '+' or '/' from standard base64, padding '=', or whitespace).

Source

Thrown at packages/coding-agent/src/modes/rpc/rpc-messages.ts:55

interface RpcMessageCursorPayload extends RpcMessageSnapshot {
	version: 1;
	offset: number;
}

export interface RpcMessagesPageOptions {
	cursor?: string;
	limit?: number;
}

function encodeCursor(snapshot: RpcMessageSnapshot, offset: number): string {
	const payload: RpcMessageCursorPayload = { version: 1, ...snapshot, offset };
	return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
}

function decodeCursor(cursor: string): RpcMessageCursorPayload {
	if (cursor.length === 0 || cursor.length > MAX_RPC_MESSAGE_CURSOR_CHARS || !/^[A-Za-z0-9_-]+$/.test(cursor))
		throw new Error("Invalid RPC message cursor");
	const bytes = Buffer.from(cursor, "base64url");
	if (bytes.toString("base64url") !== cursor) throw new Error("Invalid RPC message cursor");
	let value: unknown;
	try {
		value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
	} catch {
		throw new Error("Invalid RPC message cursor");
	}
	if (!isRecord(value)) throw new Error("Invalid RPC message cursor");
	const { version, sessionId, leafId, messageCount, offset } = value;
	if (
		version !== 1 ||
		typeof sessionId !== "string" ||
		sessionId.length === 0 ||
		sessionId.length > 256 ||
		!((typeof leafId === "string" && leafId.length > 0 && leafId.length <= 256) || leafId === null) ||
		typeof messageCount !== "number" ||
		!Number.isSafeInteger(messageCount) ||

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the cursor string back verbatim exactly as returned in nextCursor — no decoding, trimming, padding, or re-encoding.
  2. If storing in a URL, use encodeURIComponent or keep it in a path-safe context; base64url chars are already URL-safe so no transformation is needed.
  3. Trim environment/file-read artifacts (strip whitespace/newlines only — do not alter core characters).
  4. If the cursor came from an older session, treat it as invalid and restart pagination without a cursor (offset 0) instead of passing garbage.

Example fix

// before
const cursor = fs.readFileSync(path, 'utf8'); // may include trailing \n
await rpc.page({ cursor });
// after
const cursor = fs.readFileSync(path, 'utf8').trim();
if (cursor && /^[A-Za-z0-9_-]+$/.test(cursor)) await rpc.page({ cursor });
Defensive patterns

Strategy: validation

Validate before calling

function isPlausibleCursor(c: unknown): c is string {
  return typeof c === "string" && c.length > 0 && c.length <= 2048 && /^[A-Za-z0-9_-]+$/.test(c);
}
if (cursor !== undefined && !isPlausibleCursor(cursor)) cursor = undefined; // restart from page 1

Type guard

function isCursorShape(v: unknown): v is string {
  return typeof v === "string" && /^[A-Za-z0-9_-]{1,2048}$/.test(v);
}

Try / catch

try {
  return pageRpcMessages(messages, snapshot, { cursor });
} catch (err) {
  if (err.message === "Invalid RPC message cursor") {
    return pageRpcMessages(messages, snapshot, {}); // restart pagination
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling pageRpcMessages/get_messages_page with a cursor that is '', longer than 2048 chars, contains '=' padding, standard-base64 '+/' characters, URL-encoded characters, or surrounding whitespace/newline.

Common situations: Client stores the cursor in a URL and something URL-decodes or re-encodes it (base64url → base64); cursor passed through JSON form round-trip with escaping; a client fabricates or hand-truncates a cursor; cursor persisted with a trailing newline in a file.

Related errors


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