mastra-ai/mastra · error · Error

Knowledge node cursor does not match the active browse filte

Error message

Knowledge node cursor does not match the active browse filters.

What it means

parseKnowledgeNodeCursor embeds the active browse filters (namePrefix lowercased, kind, hasContent) into each cursor token created by createKnowledgeNodeCursor. When a cursor is passed back to listNodes, it is decoded and its embedded filter fingerprint is compared against the filters of the current request; any mismatch (or a malformed cursor missing version:1/type:'node'/valid fields) throws this error. This prevents cursors from silently resuming a differently-filtered listing with wrong pagination semantics.

Source

Thrown at packages/core/src/storage/domains/knowledge/base.ts:204

  try {
    value = JSON.parse(decodeURIComponent(cursor));
  } catch {
    throw new Error('Invalid knowledge node cursor.');
  }
  if (!value || typeof value !== 'object') throw new Error('Invalid knowledge node cursor.');
  const parsed = value as Record<string, unknown>;
  const updatedAt = typeof parsed.updatedAt === 'string' ? new Date(parsed.updatedAt) : new Date(Number.NaN);
  if (
    parsed.version !== 1 ||
    parsed.type !== 'node' ||
    typeof parsed.name !== 'string' ||
    typeof parsed.id !== 'string' ||
    Number.isNaN(updatedAt.getTime()) ||
    parsed.namePrefix !== (filters.namePrefix?.toLocaleLowerCase() ?? null) ||
    parsed.kind !== (filters.kind ?? null) ||
    parsed.hasContent !== (filters.hasContent ?? null)
  ) {
    throw new Error('Knowledge node cursor does not match the active browse filters.');
  }
  return { updatedAt, name: parsed.name, id: parsed.id };
}

/** @experimental Knowledge APIs are experimental and may change without notice. */
export interface QueryKnowledgeInput {
  node: KnowledgeNodeReference;

  scope: KnowledgeScope;
  after?: string;
  limit?: number;
  includeDeleted?: boolean;
}

/** @experimental Knowledge APIs are experimental and may change without notice. */
export interface QueryKnowledgeOutput {
  records: KnowledgeRecord[];
  nextCursor?: string;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Always echo back the exact cursor string returned by the previous listNodes call together with the IDENTICAL filters object used in that call.
  2. If filters changed, restart pagination: drop the cursor and issue a fresh first-page request with the new filters.
  3. Regenerate cursors with createKnowledgeNodeCursor(lastNode, filters) only if you intentionally keep the same filters.
  4. Ensure the cursor is not truncated or mangled by URL encoding/decoding (it is encodeURIComponent-wrapped JSON).

Example fix

// before
const page2 = await storage.listNodes({ scope, kind: 'note', cursor: page1Cursor });
// after
const page2 = await storage.listNodes({ scope, kind: 'doc', cursor: page1Cursor }); // same filters as the request that produced page1Cursor
Defensive patterns

Strategy: try-catch

Validate before calling

// Store {cursor, filters} together and compare before resuming:
function canResumeCursor(saved: { cursor: string; filters: { namePrefix?: string; kind?: string; hasContent?: boolean } }, current: typeof saved.filters) {
  return (saved.filters.kind ?? null) === (current.kind ?? null)
    && (saved.filters.namePrefix?.toLocaleLowerCase() ?? null) === (current.namePrefix?.toLocaleLowerCase() ?? null)
    && (saved.filters.hasContent ?? null) === (current.hasContent ?? null);
}

Try / catch

try {
  return await storage.listNodes({ ...filters, cursor });
} catch (e) {
  if (e instanceof Error && e.message.includes('does not match the active browse filters')) {
    return await storage.listNodes({ ...filters }); // restart pagination with new filters
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling KnowledgeStorage.listNodes (or equivalent browse API) with a cursor string obtained from a previous listNodes call that used different filters — e.g. page 1 with kind:'doc', page 2 with kind:'note'; or adding/removing namePrefix or hasContent between pages; or passing a cursor from listNodes into a differently-shaped browse call.

Common situations: Pagination state stored client-side (URL query param, React state) while the user edits the search/filter UI before fetching the next page; server restarting with a changed default filter; serializing cursors across API versions or tenants; hand-crafting cursors instead of using the returned nextCursor.

Related errors


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