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

  1. Keep namePrefix/kind/sort/level identical to the request that minted the cursor; if filters change, restart pagination without a cursor.
  2. Catch KnowledgeInspectorError 'invalid-cursor', drop the cursor, and re-issue the first page.
  3. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/909ba6dde9ce0a37. Report an issue: GitHub.