mastra-ai/mastra · error

Cursor is required

Error message

Cursor is required

What it means

The observational-memory recall tooling resolves a `cursor` parameter to a specific message before reading context around it. `resolveCursorMessage` first trims the cursor and throws immediately if the result is empty, because an empty cursor cannot identify any message. This is an input-validation guard: callers must pass a non-empty, single message ID string.

Source

Thrown at packages/memory/src/tools/om-tools.ts:128

  // Colon-delimited range: "startId:endId"
  const colonIndex = cursor.indexOf(':');
  if (colonIndex > 0 && colonIndex < cursor.length - 1) {
    return { startId: cursor.slice(0, colonIndex), endId: cursor.slice(colonIndex + 1) };
  }

  return null;
}

async function resolveCursorMessage(
  memory: RecallMemory,
  cursor: string,
  access?: { resourceId?: string; threadScope?: string; enforceThreadScope?: boolean },
): Promise<MastraDBMessage | { hint: string; startId: string; endId: string }> {
  const normalized = cursor.trim();

  if (!normalized) {
    throw new Error('Cursor is required');
  }

  const rangeIds = parseRangeFormat(normalized);
  if (rangeIds) {
    return {
      hint: `The cursor "${cursor}" looks like a range. Use one of the individual message IDs as the cursor instead: start="${rangeIds.startId}" or end="${rangeIds.endId}".`,
      ...rangeIds,
    };
  }

  const memoryStore = await memory.getMemoryStore();
  const result = await memoryStore.listMessagesById({ messageIds: [normalized] });
  let message = result.messages.find(message => message.id === normalized) ?? null;

  if (!message) {
    message = await resolveCursorMessageByRecall(memory, normalized, access);
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pass a valid non-empty message ID as the cursor; use the messageId returned by a previous recall/list call.
  2. If there is no cursor to continue from, omit the cursor-dependent call and use plain paged recall (page/limit) instead.
  3. Validate the cursor before calling: if (!cursor?.trim()) skip or default the call.
  4. If the model produced the empty value, improve the tool description/examples so the agent knows to copy an exact messageId.

Example fix

// before
await recallMessages({ memory, threadId, cursor: userCursor });
// after
if (!userCursor?.trim()) {
  throw new Error('No cursor provided; start with paged recall instead.');
}
await recallMessages({ memory, threadId, cursor: userCursor.trim() });
Defensive patterns

Strategy: validation

Validate before calling

function hasCursor(cursor: unknown): cursor is string {
  return typeof cursor === 'string' && cursor.trim().length > 0;
}
if (!hasCursor(cursor)) throw new Error('Provide a non-empty messageId cursor or omit the call.');

Type guard

function isNonEmptyString(v: unknown): v is string {
  return typeof v === 'string' && v.trim().length > 0;
}

Try / catch

try {
  await recallMessages({ memory, threadId, cursor });
} catch (e) {
  if (e instanceof Error && e.message === 'Cursor is required') {
    // fall back to paged recall from page 1
    return recallMessages({ memory, threadId, page: 1 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling recall/recallMessages or any om-tool path that passes cursor="" or cursor consisting only of whitespace (e.g. an unset variable interpolated as empty string, cursor: '' from a tool schema default).

Common situations: A model (LLM) fills in the cursor argument with an empty string because it has no previous recall result to continue from; an app passes an optional cursor variable that was never initialized; a client omits the field and downstream code coerces undefined to ''.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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