{"record":{"id":"c1944a6fb95013d6","repo":"koala73/worldmonitor","slug":"invalid-cursor","errorCode":"invalid_cursor","errorMessage":"cursor is not a valid catalog cursor.","messagePattern":"cursor is not a valid catalog cursor\\.","errorType":"validation","errorClass":"DashboardPanelCatalogError","httpStatus":null,"severity":"error","filePath":"src/services/webmcp-panel-catalog.ts","lineNumber":193,"sourceCode":"      'enabled must be a boolean.',\n    );\n  }\n  if (query.available !== undefined && typeof query.available !== 'boolean') {\n    throw new DashboardPanelCatalogError(\n      'malformed_arguments',\n      'available must be a boolean.',\n    );\n  }\n\n  const cursor = query.cursor;\n  if (cursor !== undefined) {\n    if (\n      typeof cursor !== 'string'\n      || cursor.length > DASHBOARD_PANEL_ID_MAX_CHARS\n      || !PANEL_ID_RE.test(cursor)\n      || !CANONICAL_PANEL_ID_SET.has(cursor)\n    ) {\n      throw new DashboardPanelCatalogError(\n        'invalid_cursor',\n        'cursor is not a valid catalog cursor.',\n      );\n    }\n  }\n\n  const limit = query.limit ?? DASHBOARD_PANEL_CATALOG_DEFAULT_LIMIT;\n  if (\n    !Number.isInteger(limit)\n    || limit < 1\n    || limit > DASHBOARD_PANEL_CATALOG_MAX_LIMIT\n  ) {\n    throw new DashboardPanelCatalogError(\n      'invalid_limit',\n      `limit must be an integer from 1 to ${DASHBOARD_PANEL_CATALOG_MAX_LIMIT}.`,\n    );\n  }\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/webmcp-panel-catalog.ts#L175-L211","documentation":"Pagination cursors in listDashboardPanelCatalog() are the panel id of the last item on the previous page. A cursor must be a string of at most DASHBOARD_PANEL_ID_MAX_CHARS chars, match the panel-id pattern (PANEL_ID_RE), and exist in CANONICAL_PANEL_ID_SET (canonical panel ids, excluding cw-/mcp- internal ids). Anything else throws DashboardPanelCatalogError with reason 'invalid_cursor'.","triggerScenarios":"Passing cursor values that are opaque tokens ('page2', base64), an id filtered out of the canonical set (a 'cw-' or 'mcp-' panel id), an id longer than 96 chars, a malformed/empty string, or a panel id that existed when the cursor was issued but was later removed from ALL_PANELS.","commonSituations":"Inventing cursors instead of using nextCursor from the previous page; persisting cursors across releases where the panel registry changed; passing an internal (cw-/mcp-) panel id obtained from another subsystem.","solutions":["Only use the exact nextCursor value returned by the previous listDashboardPanelCatalog response; never construct cursors manually.","If a stored cursor fails, discard it and restart pagination from the first page (omit cursor).","Validate the candidate cursor against getCanonicalDashboardPanelIds() (getCanonicalDashboardPanelIds().includes(cursor)) before calling.","Catch DashboardPanelCatalogError with reason 'invalid_cursor' and transparently retry without the cursor."],"exampleFix":"// before\nlistDashboardPanelCatalog(live, { cursor: savedState.cursor ?? 'start' });\n// after\nconst cursor = savedState.cursor && getCanonicalDashboardPanelIds().includes(savedState.cursor)\n  ? savedState.cursor\n  : undefined;\nlistDashboardPanelCatalog(live, cursor ? { cursor } : {});","handlingStrategy":"try-catch","validationCode":"import { getCanonicalDashboardPanelIds } from '@/services/webmcp-panel-catalog';\nconst validCursor = typeof cursor === 'string' && cursor.length <= 96\n  && getCanonicalDashboardPanelIds().includes(cursor)\n  ? cursor\n  : undefined;","typeGuard":"function isValidCatalogCursor(value: unknown): value is string {\n  return typeof value === 'string'\n    && value.length <= 96\n    && getCanonicalDashboardPanelIds().includes(value);\n}","tryCatchPattern":"try {\n  return listDashboardPanelCatalog(live, { cursor, ...query });\n} catch (err) {\n  if (err instanceof DashboardPanelCatalogError && err.reason === 'invalid_cursor') {\n    return listDashboardPanelCatalog(live, query); // restart from page 1\n  }\n  throw err;\n}","preventionTips":["Always take cursors verbatim from the previous response's nextCursor; never fabricate them.","Treat cursors as opaque and short-lived; discard after registry changes or releases.","Handle invalid_cursor by restarting pagination from the first page rather than failing the user flow.","Never pass internal cw-/mcp- panel ids as cursors — they are excluded from the canonical set."],"tags":["pagination","cursor","panel-catalog","validation"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}