{"record":{"id":"0a3dbb512303897e","repo":"mastra-ai/mastra","slug":"cursor-is-required","errorCode":null,"errorMessage":"Cursor is required","messagePattern":"Cursor is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/memory/src/tools/om-tools.ts","lineNumber":128,"sourceCode":"\n  // Colon-delimited range: \"startId:endId\"\n  const colonIndex = cursor.indexOf(':');\n  if (colonIndex > 0 && colonIndex < cursor.length - 1) {\n    return { startId: cursor.slice(0, colonIndex), endId: cursor.slice(colonIndex + 1) };\n  }\n\n  return null;\n}\n\nasync function resolveCursorMessage(\n  memory: RecallMemory,\n  cursor: string,\n  access?: { resourceId?: string; threadScope?: string; enforceThreadScope?: boolean },\n): Promise<MastraDBMessage | { hint: string; startId: string; endId: string }> {\n  const normalized = cursor.trim();\n\n  if (!normalized) {\n    throw new Error('Cursor is required');\n  }\n\n  const rangeIds = parseRangeFormat(normalized);\n  if (rangeIds) {\n    return {\n      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}\".`,\n      ...rangeIds,\n    };\n  }\n\n  const memoryStore = await memory.getMemoryStore();\n  const result = await memoryStore.listMessagesById({ messageIds: [normalized] });\n  let message = result.messages.find(message => message.id === normalized) ?? null;\n\n  if (!message) {\n    message = await resolveCursorMessageByRecall(memory, normalized, access);\n  }\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/tools/om-tools.ts#L110-L146","documentation":"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.","triggerScenarios":"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).","commonSituations":"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 ''.","solutions":["Pass a valid non-empty message ID as the cursor; use the messageId returned by a previous recall/list call.","If there is no cursor to continue from, omit the cursor-dependent call and use plain paged recall (page/limit) instead.","Validate the cursor before calling: if (!cursor?.trim()) skip or default the call.","If the model produced the empty value, improve the tool description/examples so the agent knows to copy an exact messageId."],"exampleFix":"// before\nawait recallMessages({ memory, threadId, cursor: userCursor });\n// after\nif (!userCursor?.trim()) {\n  throw new Error('No cursor provided; start with paged recall instead.');\n}\nawait recallMessages({ memory, threadId, cursor: userCursor.trim() });","handlingStrategy":"validation","validationCode":"function hasCursor(cursor: unknown): cursor is string {\n  return typeof cursor === 'string' && cursor.trim().length > 0;\n}\nif (!hasCursor(cursor)) throw new Error('Provide a non-empty messageId cursor or omit the call.');","typeGuard":"function isNonEmptyString(v: unknown): v is string {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  await recallMessages({ memory, threadId, cursor });\n} catch (e) {\n  if (e instanceof Error && e.message === 'Cursor is required') {\n    // fall back to paged recall from page 1\n    return recallMessages({ memory, threadId, page: 1 });\n  }\n  throw e;\n}","preventionTips":["Only pass cursors copied verbatim from prior tool responses.","Treat cursor as optional in app code and switch to paged recall when absent.","Whitelist the tool argument so an LLM cannot submit an empty string."],"tags":["validation","memory","cursor","observational-memory"],"backgroundTag":"missing-required-parameter","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}