{"record":{"id":"1f9dafbab36cd769","repo":"paperclipai/paperclip","slug":"paperclip-current-wake-comments-cursor-invalid","errorCode":"paperclip_current_wake_comments_cursor_invalid","errorMessage":"paperclip_current_wake_comments_cursor_invalid","messagePattern":"paperclip_current_wake_comments_cursor_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/current-wake-comments.ts","lineNumber":355,"sourceCode":"  };\n}\n\nfunction encodeCursor(cursor: CurrentWakeCommentsCursor): string {\n  return Buffer.from(JSON.stringify(cursor), \"utf8\").toString(\"base64url\");\n}\n\nfunction decodeCursor(\n  value: unknown,\n  binding: CurrentWakeCommentsBinding,\n  snapshotDigest: string,\n): CurrentWakeCommentsCursor | null {\n  if (value === undefined || value === null) return null;\n  if (\n    typeof value !== \"string\" ||\n    value.length === 0 ||\n    value.length > MAX_CURSOR_CHARS\n  ) {\n    throw new Error(\"paperclip_current_wake_comments_cursor_invalid\");\n  }\n  try {\n    const parsed = record(\n      JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\")),\n    );\n    if (\n      parsed.schema !== \"paperclip.current-wake-comments-cursor.v1\" ||\n      parsed.bindingDigest !== binding.bindingDigest ||\n      parsed.snapshotDigest !== snapshotDigest ||\n      !Number.isSafeInteger(parsed.commentIndex) ||\n      !Number.isSafeInteger(parsed.bodyOffset) ||\n      Number(parsed.commentIndex) < 0 ||\n      Number(parsed.bodyOffset) < 0\n    ) {\n      throw new Error(\"paperclip_current_wake_comments_cursor_invalid\");\n    }\n    return parsed as CurrentWakeCommentsCursor;\n  } catch (error) {","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/current-wake-comments.ts#L337-L373","documentation":"decodeCursor validates the opaque base64url pagination cursor for the current wake comments endpoint. It throws when the cursor is not a non-empty string or exceeds MAX_CURSOR_CHARS, before even attempting to decode it. This is the first of several validation gates that all use the same error code.","triggerScenarios":"Passing cursor=undefined/null coerced into a string call path that skips the null early-return, an empty string cursor, or a cursor string longer than MAX_CURSOR_CHARS (client-supplied garbage or oversized cursor from another endpoint).","commonSituations":"Clients sending `?cursor=` (empty query param) instead of omitting it; storing cursors that were padded/altered in transit (URL encoding, truncation by logs or proxies); frontends persisting a cursor from a different endpoint with a larger payload.","solutions":["Omit the cursor parameter entirely for the first page instead of sending an empty string.","Use the cursor value exactly as returned by the API, unmodified (no re-encoding or truncation).","Check cursor length against MAX_CURSOR_CHARS before sending; re-fetch page 1 if the stored cursor is too long.","Ensure the cursor came from the same endpoint's prior response, not a different comments API."],"exampleFix":"// before\nconst url = `/api/comments?cursor=${cursor ?? \"\"}`;\n// after\nconst url = cursor ? `/api/comments?cursor=${encodeURIComponent(cursor)}` : \"/api/comments\";","handlingStrategy":"validation","validationCode":"const cursorOk = typeof cursor === \"string\" && cursor.length > 0 && cursor.length <= 4096;\nif (cursor && !cursorOk) throw new Error(\"client: cursor must be a non-empty, unmodified string\");","typeGuard":"const isUsableCursor = (c: unknown): c is string => typeof c === \"string\" && c.length > 0 && c.length <= 4096;","tryCatchPattern":"try { return await fetchComments({ cursor }); } catch (e) { if (e.message.includes(\"cursor_invalid\")) return fetchComments({}); // restart from page 1\n throw e; }","preventionTips":["Omit the cursor param for the first page instead of sending an empty string.","Echo cursors back verbatim with no re-encoding, padding, or truncation.","Persist cursors only from the same endpoint that issued them."],"tags":["pagination","cursor","validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}