can1357/oh-my-pi · error
Invalid ACP session cursor: ${cursor}
Error message
Invalid ACP session cursor: ${cursor} What it means
#parseCursor converts the ACP pagination cursor into a non-negative integer message offset. An absent cursor means 0 (start), but a present cursor that is not a finite non-negative integer is rejected with this error. It protects history replay from garbage cursors that would silently truncate or mis-index the transcript.
Source
Thrown at packages/coding-agent/src/modes/acp/acp-agent.ts:2242
// naming scheme. Sessions written under a legacy/hashed project directory
// (the 17.2.5+ scheme reverted in #7656) live elsewhere, so fall back to a
// global by-id scan: the session id is globally unique, and
// #openStoredSession reopens the file with the request cwd. See #7779.
return this.#findStoredSessionById(sessionId);
}
async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
const sessions = await this.#listStoredSessions();
return sessions.find(session => session.id === sessionId);
}
#parseCursor(cursor: string | undefined): number {
if (!cursor) {
return 0;
}
const parsed = Number.parseInt(cursor, 10);
if (!Number.isFinite(parsed) || parsed < 0) {
throw new Error(`Invalid ACP session cursor: ${cursor}`);
}
return parsed;
}
async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
const cwd = record.session.sessionManager.getCwd();
const replayedToolCallIds = new Set<string>();
const replayedToolCallArgs = new Map<string, unknown>();
for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
for (const notification of this.#messageToReplayNotifications(
record.session.sessionId,
message,
cwd,
replayedToolCallIds,
replayedToolCallArgs,
)) {
await this.#connection.sessionUpdate(notification);
}View on GitHub (pinned to 9690622007)
Solutions
- Send the cursor string exactly as returned by the previous page response, unmodified.
- Omit the cursor entirely (or send undefined) to request history from the beginning.
- If implementing a client, store cursors opaquely and pass them back verbatim rather than parsing them.
Example fix
// before
const history = await loadHistory({ cursor: "page-3" });
// after
const history = await loadHistory({ cursor: previousResponse.nextCursor }); // opaque token, or omit for start Defensive patterns
Strategy: validation
Validate before calling
function isValidCursor(cursor?: string): boolean {
if (cursor === undefined) return true;
const n = Number.parseInt(cursor, 10);
return Number.isFinite(n) && n >= 0 && String(n) === cursor;
}
if (!isValidCursor(cursor)) cursor = undefined; // restart from beginning Type guard
function isCursor(v: unknown): v is string {
return typeof v === "string" && /^\d+$/.test(v);
} Try / catch
try {
return await loadHistory({ cursor });
} catch (err) {
if (err.message.startsWith("Invalid ACP session cursor")) {
return await loadHistory({}); // restart from the beginning
} throw err;
} Prevention
- Treat cursors as opaque tokens: store and echo them verbatim.
- Never build cursors by hand or transform them (encoding, prefixes).
- Reset to the beginning (omit cursor) when a cursor is rejected.
When it happens
Trigger: Calling an ACP session/history-loading RPC with a cursor query value that is empty-but-present, non-numeric, negative, or otherwise unparseable by Number.parseInt (e.g. "abc", "-1", "1.5.2").
Common situations: A client round-trips the cursor through something that corrupts it (URL encoding, prefixing); a hand-written script guesses a cursor format; a client sends a page-token from a different API.
Related errors
- ACP cwd must be absolute: ${cwd}
- Unsupported ACP mode: ${modeId}
- Invalid RPC message cursor
- RPC message page limit must be between 1 and ${MAX_RPC_MESSA
- skip must be a non-negative number
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f06074abb4e83310.
Report an issue: GitHub.