mastra-ai/mastra · error · KnowledgeInspectorError
invalid-cursor
invalid-cursor
Error message
Knowledge cursor does not match the active scope and filters.
What it means
Pagination cursors are opaque tokens bound to identityKey, scope level, kind (nodes/activity), and the exact filters (namePrefix, kind, sort). #consumeCursor() throws 'invalid-cursor' if the cursor is unknown, expired, from a different scope/level, or if any filter differs from the original query. Cursors cannot be reused across differently-filtered requests.
Source
Thrown at mastracode/sdk/src/knowledge-inspector.ts:736
cursor: string | undefined,
binding: Binding,
level: KnowledgeInspectorScopeLevel,
kind: CursorEntry['kind'],
filters?: CursorEntry['filters'],
): string | undefined {
if (!cursor) return undefined;
const entry = this.#cursors.get(cursor);
if (
!entry ||
entry.expiresAt < Date.now() ||
entry.identityKey !== binding.identityKey ||
entry.level !== level ||
entry.kind !== kind ||
entry.filters?.namePrefix !== filters?.namePrefix ||
entry.filters?.kind !== filters?.kind ||
entry.filters?.sort !== filters?.sort
) {
throw new KnowledgeInspectorError(
'invalid-cursor',
'Knowledge cursor does not match the active scope and filters.',
);
}
return entry.value;
}
#pruneOpaqueEntries(): void {
const now = Date.now();
for (const [token, entry] of this.#handles) {
if (entry.expiresAt < now) this.#handles.delete(token);
}
for (const [token, entry] of this.#cursors) {
if (entry.expiresAt < now) this.#cursors.delete(token);
}
while (this.#handles.size + this.#cursors.size >= MAX_OPAQUE_ENTRIES) {
const handle = this.#handles.keys().next().value;
if (handle) this.#handles.delete(handle);View on GitHub (pinned to 75dd419e61)
Solutions
- Keep namePrefix/kind/sort/level identical to the request that minted the cursor; if filters change, restart pagination without a cursor.
- Catch KnowledgeInspectorError 'invalid-cursor', drop the cursor, and re-issue the first page.
- Use cursors soon after minting; never persist them across sessions or scope switches.
Example fix
// before
await inspector.listNodes({ level: 'resource', sort: 'recent', cursor: cursorForSortConnected });
// after
await inspector.listNodes({ level: 'resource', sort: 'connected', cursor: cursorForSortConnected }); // filters match minting request Defensive patterns
Strategy: try-catch
Try / catch
try {
return await inspector.listNodes({ level, namePrefix, kind, sort, cursor });
} catch (e) {
if (e instanceof KnowledgeInspectorError && e.code === 'invalid-cursor') {
return inspector.listNodes({ level, namePrefix, kind, sort }); // restart pagination from page 1
}
throw e;
} Prevention
- Freeze filter state (namePrefix, kind, sort, level) while a cursor is in use; changing filters resets pagination.
- Store cursor together with the exact query params that minted it and verify before reuse.
- Expire client-side pagination state with the session/scope, not across restarts.
When it happens
Trigger: Passing a nextCursor from a listNodes query into a subsequent call with changed namePrefix/kind/sort or a different level; reusing a cursor after a scope change (identityKey rotation); using a cursor after TTL expiry or cap eviction.
Common situations: UI changing the sort/filter while a 'load more' uses the old cursor; sharing cursors between org- and thread-level views; paginating hours later after the cursor expired.
Related errors
- GitHub cursor must be a positive page number.
- GitHub cursor must be a positive page number.
- Attention read-all response is missing its continuation curs
- stale-handle
- not-visible
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/909ba6dde9ce0a37.
Report an issue: GitHub.