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
- Pass the cursor string back verbatim exactly as returned in nextCursor — no decoding, trimming, padding, or re-encoding.
- 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.
- Trim environment/file-read artifacts (strip whitespace/newlines only — do not alter core characters).
- 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
- Pass nextCursor back verbatim; never decode/trim/re-encode it
- base64url is already URL-safe — do not apply additional URL decoding
- Strip only surrounding whitespace when reading cursors from files or storage
- Validate persisted cursors on load and fall back to page 1 if malformed
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
- RPC message pagination repeated a cursor
- RPC message page limit must be between 1 and ${MAX_RPC_MESSA
- stale_cursor
- RPC message pagination repeated a cursor
- Unsupported language '{value}'. Supported: {}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b9ff0691454fd6f5.
Report an issue: GitHub.