paperclipai/paperclip · error

paperclip_current_wake_comments_cursor_invalid

paperclip_current_wake_comments_cursor_invalid

Error message

paperclip_current_wake_comments_cursor_invalid

What it means

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.

Solutions

  1. Omit the cursor parameter entirely for the first page instead of sending an empty string.
  2. Use the cursor value exactly as returned by the API, unmodified (no re-encoding or truncation).
  3. Check cursor length against MAX_CURSOR_CHARS before sending; re-fetch page 1 if the stored cursor is too long.
  4. Ensure the cursor came from the same endpoint's prior response, not a different comments API.

Example fix

// before
const url = `/api/comments?cursor=${cursor ?? ""}`;
// after
const url = cursor ? `/api/comments?cursor=${encodeURIComponent(cursor)}` : "/api/comments";
Defensive patterns

Strategy: validation

Validate before calling

const cursorOk = typeof cursor === "string" && cursor.length > 0 && cursor.length <= 4096;
if (cursor && !cursorOk) throw new Error("client: cursor must be a non-empty, unmodified string");

Type guard

const isUsableCursor = (c: unknown): c is string => typeof c === "string" && c.length > 0 && c.length <= 4096;

Try / catch

try { return await fetchComments({ cursor }); } catch (e) { if (e.message.includes("cursor_invalid")) return fetchComments({}); // restart from page 1
 throw e; }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/1f9dafbab36cd769. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/native-runtime/current-wake-comments.ts:355

  };
}

function encodeCursor(cursor: CurrentWakeCommentsCursor): string {
  return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
}

function decodeCursor(
  value: unknown,
  binding: CurrentWakeCommentsBinding,
  snapshotDigest: string,
): CurrentWakeCommentsCursor | null {
  if (value === undefined || value === null) return null;
  if (
    typeof value !== "string" ||
    value.length === 0 ||
    value.length > MAX_CURSOR_CHARS
  ) {
    throw new Error("paperclip_current_wake_comments_cursor_invalid");
  }
  try {
    const parsed = record(
      JSON.parse(Buffer.from(value, "base64url").toString("utf8")),
    );
    if (
      parsed.schema !== "paperclip.current-wake-comments-cursor.v1" ||
      parsed.bindingDigest !== binding.bindingDigest ||
      parsed.snapshotDigest !== snapshotDigest ||
      !Number.isSafeInteger(parsed.commentIndex) ||
      !Number.isSafeInteger(parsed.bodyOffset) ||
      Number(parsed.commentIndex) < 0 ||
      Number(parsed.bodyOffset) < 0
    ) {
      throw new Error("paperclip_current_wake_comments_cursor_invalid");
    }
    return parsed as CurrentWakeCommentsCursor;
  } catch (error) {

View on GitHub (pinned to 3f1d897a7c)