mastra-ai/mastra · error

The cursor "${cursor}" looks like a range. Use one of the in

Error message

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}".

What it means

Cursors must be a single message ID. `parseRangeFormat` detects range-shaped cursors like "startId:endId" or "id1:id2,id3:id4" (the internal format of merged ranges) and `resolveCursorMessage` returns a hint object; the detail-recall path converts that hint into this thrown error. The hint itself contains the correct start/end IDs to use instead.

Source

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

  nextCharOffset?: number;
  note?: string;
}> {
  if (!memory || typeof memory.getMemoryStore !== 'function') {
    throw new Error('Memory instance is required for recall');
  }

  if (!threadId) {
    throw new Error('Thread ID is required for recall');
  }

  const resolved = await resolveCursorMessage(memory, cursor, {
    resourceId,
    threadScope,
    enforceThreadScope: false,
  });

  if ('hint' in resolved) {
    throw new Error(resolved.hint);
  }

  const allParts = formatMessageParts(resolved, 'high');

  if (allParts.length === 0) {
    throw new Error(
      `Message ${cursor} has no visible content (it may be an internal system message). Try a neighboring message ID instead.`,
    );
  }

  const target = [...allParts].reverse().find(p => p.partIndex === partIndex);

  if (!target) {
    const availableIndices = allParts.map(p => p.partIndex).join(', ');
    const highestVisiblePartIndex = Math.max(...allParts.map(p => p.partIndex));

    if (partIndex > highestVisiblePartIndex) {
      const nextMessage = await getNextVisibleMessage({

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Parse the error message: it names start="..." and end="..." — use one of those individual IDs as the cursor.
  2. Use the start ID to recall forward from the beginning of the range, or the end ID to end at it.
  3. Never pass multi-message range strings to the recall cursor parameter; ranges are internal to observational-memory processing.
  4. If the model keeps doing this, add an example in the tool description showing a single plain message ID.

Example fix

// before
await recallMessages({ memory, threadId, cursor: 'msg_1:msg_50' });
// after
await recallMessages({ memory, threadId, cursor: 'msg_1' });
Defensive patterns

Strategy: validation

Validate before calling

function isSingleMessageId(cursor: string): boolean {
  return cursor.trim().length > 0 && !cursor.includes(':') && !cursor.includes(',');
}
if (!isSingleMessageId(cursor)) throw new Error('Use a single message ID, not a start:end range.');

Try / catch

try {
  return await recallDetail({ memory, threadId, cursor, partIndex });
} catch (e) {
  const m = e instanceof Error && /start="([^"]+)" or end="([^"]+)"/.exec(e.message);
  if (m) {
    return recallDetail({ memory, threadId, cursor: m[1], partIndex }); // use the start ID
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a range cursor copied from internal/observational-memory state (e.g. "msgA:msgB") into the recall tool's cursor argument; an LLM echoing back a range it saw in tool output instead of one endpoint ID.

Common situations: Piping output from another om feature that emits ranges directly into recall; hand-building cursors by concatenating two IDs; model misreading the cursor documentation.

Related errors


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