{"record":{"id":"fae659e6dee3134a","repo":"mastra-ai/mastra","slug":"message-cursor-has-no-visible-content-it-may-b","errorCode":null,"errorMessage":"Message ${cursor} has no visible content (it may be an internal system message). Try a neighboring message ID instead.","messagePattern":"Message (.+?) has no visible content \\(it may be an internal system message\\)\\. Try a neighboring message ID instead\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/memory/src/tools/om-tools.ts","lineNumber":811,"sourceCode":"\n  if (!threadId) {\n    throw new Error('Thread ID is required for recall');\n  }\n\n  const resolved = await resolveCursorMessage(memory, cursor, {\n    resourceId,\n    threadScope,\n    enforceThreadScope: false,\n  });\n\n  if ('hint' in resolved) {\n    throw new Error(resolved.hint);\n  }\n\n  const allParts = formatMessageParts(resolved, 'high');\n\n  if (allParts.length === 0) {\n    throw new Error(\n      `Message ${cursor} has no visible content (it may be an internal system message). Try a neighboring message ID instead.`,\n    );\n  }\n\n  const target = [...allParts].reverse().find(p => p.partIndex === partIndex);\n\n  if (!target) {\n    const availableIndices = allParts.map(p => p.partIndex).join(', ');\n    const highestVisiblePartIndex = Math.max(...allParts.map(p => p.partIndex));\n\n    if (partIndex > highestVisiblePartIndex) {\n      const nextMessage = await getNextVisibleMessage({\n        memory,\n        threadId,\n        resourceId,\n        after: resolved.createdAt,\n      });\n","sourceCodeStart":793,"sourceCodeEnd":829,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/tools/om-tools.ts#L793-L829","documentation":"After resolving the cursor message, the reader formats its visible parts; if the message yields zero visible parts (content is a bare string with no parts, or all parts are internal `data-*` parts such as tool/internal metadata), it throws this error directing the caller to a neighboring message. Internal system messages are stored but not meant to be surfaced as recall anchors.","triggerScenarios":"Using a cursor that points to a system/internal message (e.g. a data-part-only message, an empty string-content message, or a tool-internal record) as the recall anchor; picking the last ID from storage metadata rather than from formatted recall output.","commonSituations":"Model picked a messageId that appeared in logs/storage but was filtered from visible recall output; threads seeded with system messages where index 0 is internal; cursors persisted from an older schema whose messages now format as empty.","solutions":["Choose a message ID that appeared in visible recall output (user/assistant text) instead of one from raw storage.","Use the previous or next message in the thread as the cursor, as the error suggests.","List the thread with recall detail=\"low\" to get only visible message IDs, then pick one.","If this happens persistently, inspect the message content shape — a schema/migration may have stripped parts."],"exampleFix":"// before\nconst cursor = allMessageIds[0]; // may be an internal system message\nawait recallDetail({ memory, threadId, cursor, partIndex: 0 });\n// after\nconst visible = JSON.parse((await recallMessages({ memory, threadId, page: 1 })).messages);\nconst cursor = visible[0].messageId;\nawait recallDetail({ memory, threadId, cursor, partIndex: 0 });","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await recallDetail({ memory, threadId, cursor, partIndex });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('has no visible content')) {\n    // step to a neighboring message and retry once\n    const page = await recallMessages({ memory, threadId, page: 1, limit: 50 });\n    const msgs = JSON.parse(page.messages);\n    const idx = msgs.findIndex(m => m.messageId === cursor);\n    const neighbor = msgs[idx + 1] ?? msgs[idx - 1];\n    if (neighbor) return recallDetail({ memory, threadId, cursor: neighbor.messageId, partIndex });\n  }\n  throw e;\n}","preventionTips":["Pick cursors from visible (user/assistant) messages in recall output, not raw storage IDs.","Skip index-0 cursors in threads seeded with system messages.","If a formerly valid cursor goes empty, suspect a schema/migration change to message parts."],"tags":["memory","cursor","empty-content","observational-memory"],"backgroundTag":"empty-message-content","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}