{"record":{"id":"f06074abb4e83310","repo":"can1357/oh-my-pi","slug":"invalid-acp-session-cursor-cursor","errorCode":null,"errorMessage":"Invalid ACP session cursor: ${cursor}","messagePattern":"Invalid ACP session cursor: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/acp/acp-agent.ts","lineNumber":2242,"sourceCode":"\t\t// naming scheme. Sessions written under a legacy/hashed project directory\n\t\t// (the 17.2.5+ scheme reverted in #7656) live elsewhere, so fall back to a\n\t\t// global by-id scan: the session id is globally unique, and\n\t\t// #openStoredSession reopens the file with the request cwd. See #7779.\n\t\treturn this.#findStoredSessionById(sessionId);\n\t}\n\n\tasync #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {\n\t\tconst sessions = await this.#listStoredSessions();\n\t\treturn sessions.find(session => session.id === sessionId);\n\t}\n\n\t#parseCursor(cursor: string | undefined): number {\n\t\tif (!cursor) {\n\t\t\treturn 0;\n\t\t}\n\t\tconst parsed = Number.parseInt(cursor, 10);\n\t\tif (!Number.isFinite(parsed) || parsed < 0) {\n\t\t\tthrow new Error(`Invalid ACP session cursor: ${cursor}`);\n\t\t}\n\t\treturn parsed;\n\t}\n\n\tasync #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {\n\t\tconst cwd = record.session.sessionManager.getCwd();\n\t\tconst replayedToolCallIds = new Set<string>();\n\t\tconst replayedToolCallArgs = new Map<string, unknown>();\n\t\tfor (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {\n\t\t\tfor (const notification of this.#messageToReplayNotifications(\n\t\t\t\trecord.session.sessionId,\n\t\t\t\tmessage,\n\t\t\t\tcwd,\n\t\t\t\treplayedToolCallIds,\n\t\t\t\treplayedToolCallArgs,\n\t\t\t)) {\n\t\t\t\tawait this.#connection.sessionUpdate(notification);\n\t\t\t}","sourceCodeStart":2224,"sourceCodeEnd":2260,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/acp/acp-agent.ts#L2224-L2260","documentation":"#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.","triggerScenarios":"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\").","commonSituations":"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.","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."],"exampleFix":"// before\nconst history = await loadHistory({ cursor: \"page-3\" });\n// after\nconst history = await loadHistory({ cursor: previousResponse.nextCursor }); // opaque token, or omit for start","handlingStrategy":"validation","validationCode":"function isValidCursor(cursor?: string): boolean {\n  if (cursor === undefined) return true;\n  const n = Number.parseInt(cursor, 10);\n  return Number.isFinite(n) && n >= 0 && String(n) === cursor;\n}\nif (!isValidCursor(cursor)) cursor = undefined; // restart from beginning","typeGuard":"function isCursor(v: unknown): v is string {\n  return typeof v === \"string\" && /^\\d+$/.test(v);\n}","tryCatchPattern":"try {\n  return await loadHistory({ cursor });\n} catch (err) {\n  if (err.message.startsWith(\"Invalid ACP session cursor\")) {\n    return await loadHistory({}); // restart from the beginning\n  } throw err;\n}","preventionTips":["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."],"tags":["acp","pagination","validation"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}