{"record":{"id":"ace9676557333fea","repo":"Budibase/budibase","slug":"invalid-bookmark-query-ace967","errorCode":null,"errorMessage":"Invalid bookmark query","messagePattern":"Invalid bookmark query","errorType":"validation","errorClass":"HTTPError","httpStatus":400,"severity":"warning","filePath":"packages/server/src/sdk/workspace/ai/agentLogs/shared.ts","lineNumber":193,"sourceCode":"\n  return firstDate >= secondDate\n    ? firstDate.toISOString()\n    : secondDate.toISOString()\n}\n\nexport function truncateText(value: string, maxLength = 100): string {\n  if (value.length <= maxLength) {\n    return value\n  }\n  return `${value.slice(0, maxLength)}...`\n}\n\nexport function parseBookmarkPage(bookmark?: string): number {\n  if (!bookmark) {\n    return DEFAULT_BOOKMARK_PAGE\n  }\n  if (!/^\\d+$/.test(bookmark)) {\n    throw new HTTPError(\"Invalid bookmark query\", 400)\n  }\n\n  const parsedBookmark = Number.parseInt(bookmark, 10)\n  if (!Number.isFinite(parsedBookmark) || parsedBookmark < 1) {\n    throw new HTTPError(\"Invalid bookmark query\", 400)\n  }\n\n  return parsedBookmark\n}\n\nexport function normalizeSessionLimit(limit?: number): number {\n  if (!limit || limit <= 0) {\n    return DEFAULT_SESSION_PAGE_SIZE\n  }\n\n  return Math.min(limit, MAX_SESSION_PAGE_SIZE)\n}\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/ai/agentLogs/shared.ts#L175-L211","documentation":"parseBookmarkPage validates the pagination bookmark query parameter and throws HTTPError 400 'Invalid bookmark query' when the bookmark is non-numeric. It backs the 'page' resolver, so this surfaces directly to API callers of the agent-log session endpoints.","triggerScenarios":"GET agent session endpoints with ?bookmark=abc, ?bookmark=1.5, ?bookmark=%20, or any value failing /^\\d+$/ — e.g. a client passing an opaque cursor string where a numeric page number is expected.","commonSituations":"Frontend reuses a cursor-style bookmark from another paginated API; URL encodes whitespace into the bookmark; bookmarks persisted as floats or strings with leading '+'; stale client code after the pagination scheme changed from cursors to page numbers.","solutions":["Send a positive integer as the bookmark query param, e.g. ?bookmark=2","Validate the bookmark is /^\\d+$/ in the client before calling the API, else omit it for page 1","Regenerate the bookmark from the API response's nextBookmark instead of hand-crafting it","Check for client/server pagination scheme mismatch after an API version upgrade"],"exampleFix":"// before\nconst url = `/api/agent/logs?bookmark=${cursorToken}`\n// after\nconst page = Number.parseInt(cursorToken, 10)\nconst qs = Number.isInteger(page) && page > 0 ? `?bookmark=${page}` : \"\"\nconst url = `/api/agent/logs${qs}`","handlingStrategy":"validation","validationCode":"function isValidBookmark(b: unknown): b is string {\n  return typeof b === \"string\" && /^\\d+$/.test(b)\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await api.get(\"/agent/sessions\", { bookmark })\n} catch (err) {\n  if (err instanceof HTTPError && err.status === 400) {\n    return api.get(\"/agent/sessions\") // reset to page 1\n  }\n  throw err\n}","preventionTips":["Only pass numeric page bookmarks; omit for page 1","Use the nextBookmark returned by the API","Reject cursor-style tokens before sending","Clamp values with /^\\d+$/ client-side"],"tags":["validation","pagination","http-400","agent-logs"],"backgroundTag":"invalid-pagination-parameter","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}