koala73/worldmonitor · error · DashboardPanelCatalogError
invalid_cursor
invalid_cursor
Error message
cursor is not a valid catalog cursor.
What it means
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'.
Source
Thrown at src/services/webmcp-panel-catalog.ts:193
'enabled must be a boolean.',
);
}
if (query.available !== undefined && typeof query.available !== 'boolean') {
throw new DashboardPanelCatalogError(
'malformed_arguments',
'available must be a boolean.',
);
}
const cursor = query.cursor;
if (cursor !== undefined) {
if (
typeof cursor !== 'string'
|| cursor.length > DASHBOARD_PANEL_ID_MAX_CHARS
|| !PANEL_ID_RE.test(cursor)
|| !CANONICAL_PANEL_ID_SET.has(cursor)
) {
throw new DashboardPanelCatalogError(
'invalid_cursor',
'cursor is not a valid catalog cursor.',
);
}
}
const limit = query.limit ?? DASHBOARD_PANEL_CATALOG_DEFAULT_LIMIT;
if (
!Number.isInteger(limit)
|| limit < 1
|| limit > DASHBOARD_PANEL_CATALOG_MAX_LIMIT
) {
throw new DashboardPanelCatalogError(
'invalid_limit',
`limit must be an integer from 1 to ${DASHBOARD_PANEL_CATALOG_MAX_LIMIT}.`,
);
}
View on GitHub (pinned to 9361220cc0)
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.
Example fix
// before
listDashboardPanelCatalog(live, { cursor: savedState.cursor ?? 'start' });
// after
const cursor = savedState.cursor && getCanonicalDashboardPanelIds().includes(savedState.cursor)
? savedState.cursor
: undefined;
listDashboardPanelCatalog(live, cursor ? { cursor } : {}); Defensive patterns
Strategy: try-catch
Validate before calling
import { getCanonicalDashboardPanelIds } from '@/services/webmcp-panel-catalog';
const validCursor = typeof cursor === 'string' && cursor.length <= 96
&& getCanonicalDashboardPanelIds().includes(cursor)
? cursor
: undefined; Type guard
function isValidCatalogCursor(value: unknown): value is string {
return typeof value === 'string'
&& value.length <= 96
&& getCanonicalDashboardPanelIds().includes(value);
} Try / catch
try {
return listDashboardPanelCatalog(live, { cursor, ...query });
} catch (err) {
if (err instanceof DashboardPanelCatalogError && err.reason === 'invalid_cursor') {
return listDashboardPanelCatalog(live, query); // restart from page 1
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid_limit
- invalid_variant
- invalid_category
- malformed_arguments
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/c1944a6fb95013d6.
Report an issue: GitHub.