{"record":{"id":"847d4e9e70a9ace4","repo":"mastra-ai/mastra","slug":"recall-requires-a-memory-instance-with-storage-acc","errorCode":null,"errorMessage":"recall requires a Memory instance with storage access","messagePattern":"recall requires a Memory instance with storage access","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/memory/src/tools/om-tools.ts","lineNumber":927,"sourceCode":"  cursor: string;\n  page?: number;\n  limit?: number;\n  detail?: RecallDetail;\n  partType?: 'text' | 'tool-call' | 'tool-result' | 'reasoning' | 'image' | 'file';\n  toolName?: string;\n  threadScope?: string;\n  maxTokens?: number;\n}): Promise<RecallResult> {\n  if (!memory) {\n    throw new Error('Memory instance is required for recall');\n  }\n\n  if (!threadId) {\n    throw new Error('Thread ID is required for recall');\n  }\n\n  if (typeof memory.getMemoryStore !== 'function') {\n    throw new Error('recall requires a Memory instance with storage access');\n  }\n\n  const MAX_PAGE = 50;\n  const MAX_LIMIT = 20;\n  const rawPage = page === 0 ? 1 : page;\n  const normalizedPage = Math.max(Math.min(rawPage, MAX_PAGE), -MAX_PAGE);\n  const normalizedLimit = Math.min(limit, MAX_LIMIT);\n\n  const resolved = await resolveCursorMessage(memory, cursor, {\n    resourceId,\n    threadScope,\n    enforceThreadScope: false,\n  });\n\n  if ('hint' in resolved) {\n    return {\n      messages: resolved.hint,\n      count: 0,","sourceCodeStart":909,"sourceCodeEnd":945,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/tools/om-tools.ts#L909-L945","documentation":"recallMessages() needs more than any object that resembles memory — it explicitly checks typeof memory.getMemoryStore === 'function' to prove the instance exposes storage-backed methods (getMemoryStore, recall, getThreadById). Passing a duck-typed stub, a plain config object, or a different class (e.g. a store rather than a Memory) fails this duck-type check and throws.","triggerScenarios":"Calling recallMessages({ memory: <store or mock or wrong object>, threadId, cursor }) where memory exists and threadId is set, but the object lacks a getMemoryStore method — e.g. passing a MastraStorage instance, a serialized/parsed Memory copy (methods lost through JSON), or a hand-rolled mock.","commonSituations":"Persisting a Memory instance via JSON (class methods dropped on revival); passing the memory *config* object instead of a `new Memory(config)` instance; mocking Memory in tests with only `recall` stubbed; importing Memory from mismatched package versions where the method was renamed.","solutions":["Pass a real Memory instance: new Memory({ storage, options }) from @mastra/memory, not a config object or storage instance.","If memory crossed a serialization boundary, reconstruct it with new Memory(config) on the consuming side.","Update mocks in tests to implement getMemoryStore (and recall/getThreadById) as functions.","Check @mastra/core and @mastra/memory versions align so the expected method exists."],"exampleFix":"// before\nawait recallMessages({ memory: { ...memoryConfig }, threadId, cursor });\n// after\nimport { Memory } from '@mastra/memory';\nconst memory = new Memory({ storage });\nawait recallMessages({ memory, threadId, cursor });","handlingStrategy":"type-guard","validationCode":"// before calling recallMessages\nif (typeof (memory as any)?.getMemoryStore !== 'function') {\n  throw new Error('Pass a Memory instance (new Memory(...)), not a config object or storage');\n}","typeGuard":"function isStorageBackedMemory(m: unknown): m is RecallMemory {\n  return (\n    !!m &&\n    typeof m === 'object' &&\n    typeof (m as any).getMemoryStore === 'function' &&\n    typeof (m as any).recall === 'function'\n  );\n}","tryCatchPattern":"try {\n  return await recallMessages({ memory, threadId, cursor });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('storage access')) {\n    memory = new Memory(memoryConfig); // rebuild from config\n    return await recallMessages({ memory, threadId, cursor });\n  }\n  throw err;\n}","preventionTips":["Never serialize Memory instances; reconstruct with new Memory(config) after crossing boundaries.","Keep @mastra/core and @mastra/memory versions in sync.","In tests, mock the full surface (getMemoryStore, recall, getThreadById), not just recall."],"tags":["memory","wrong-type","duck-typing","recall"],"backgroundTag":"wrong-argument-type","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}