mastra-ai/mastra · error
page must be >= 0
Error message
page must be >= 0
What it means
listMessages validates that the page argument is zero or a positive number before computing an offset. Negative pages have no meaning in offset-based pagination, so the in-memory storage domain throws immediately. This is a fail-fast argument validation, not a state error.
Source
Thrown at packages/core/src/storage/domains/memory/inmemory.ts:133
orderBy,
}: StorageListMessagesInput): Promise<StorageListMessagesOutput> {
const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
// Normalize threadId to array
const threadIds = Array.isArray(threadId) ? threadId : [threadId];
if (threadIds.length === 0 || threadIds.some(id => !id.trim())) {
throw new Error('threadId must be a non-empty string or array of non-empty strings');
}
const threadIdSet = new Set(threadIds);
const { field, direction } = this.parseOrderBy(orderBy, 'ASC');
// Normalize perPage for query (false → MAX_SAFE_INTEGER, 0 → 0, undefined → 40)
const perPage = normalizePerPage(perPageInput, 40);
if (page < 0) {
throw new Error('page must be >= 0');
}
// Prevent unreasonably large page values that could cause performance issues
const maxOffset = Number.MAX_SAFE_INTEGER / 2;
if (page * perPage > maxOffset) {
throw new Error('page value too large');
}
// Calculate offset from page
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
// When perPage is 0 with no includes, there's nothing to return.
if (perPage === 0 && (!include || include.length === 0)) {
return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
}
// Step 1: Get messages matching threadId(s) and optionally resourceId
let threadMessages = Array.from(this.db.messages.values()).filter((msg: any) => {View on GitHub (pinned to 75dd419e61)
Solutions
- Clamp page before calling: Math.max(0, page).
- Validate user/URL-supplied page numbers at the boundary (NaN check plus >= 0) before invoking storage.
- If iterating pages in a loop, start at 0 and guard decrement logic against going below zero.
Example fix
// before
const page = Number(searchParams.get('page'));
await storage.listMessages({ threadId, page });
// after
const page = Math.max(0, Number.parseInt(searchParams.get('page') ?? '0', 10) || 0);
await storage.listMessages({ threadId, page }); Defensive patterns
Strategy: validation
Validate before calling
function normalizePage(raw) {
const n = Number(raw);
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
}
await storage.listMessages({ threadId, page: normalizePage(rawPage) }); Type guard
function isValidPage(v: unknown): v is number {
return typeof v === 'number' && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
} Prevention
- Clamp every page value with Math.max(0, page) at the data-access boundary.
- Convert 1-based UI page numbers to this API's 0-based page explicitly.
- Validate parsed query params (NaN and range checks) before they reach storage.
When it happens
Trigger: Calling listMessages({ threadId, page: -1 }) or any negative page, typically from a decrementing page counter that goes below 0, or from user-supplied query params parsed with parseInt without clamping.
Common situations: UI 'previous page' buttons decrementing past the first page; URL query strings like ?page=-1; Math.ceil/floor rounding producing -0-adjacent negatives on empty datasets; hand-rolled cursors.
Related errors
- threadId must be a non-empty string or array of non-empty st
- page value too large
- COMPARE_INVALID_INPUT
- perPage must be >= 0
- page must be >= 0
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/5364b2835d434b57.
Report an issue: GitHub.