{"record":{"id":"b4e80186d4ff0704","repo":"mastra-ai/mastra","slug":"thread-not-found","errorCode":null,"errorMessage":"Thread not found","messagePattern":"Thread not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/memory/src/tools/om-tools.ts","lineNumber":1146,"sourceCode":"  limit?: number;\n  detail?: RecallDetail;\n  partType?: 'text' | 'tool-call' | 'tool-result' | 'reasoning' | 'image' | 'file';\n  toolName?: string;\n  anchor?: 'start' | 'end';\n  maxTokens?: number;\n}): Promise<RecallResult> {\n  if (!memory) {\n    throw new Error('Memory instance is required for recall');\n  }\n  if (!threadId) {\n    throw new Error('Thread ID is required for recall');\n  }\n\n  // Verify the thread belongs to the current resource\n  if (resourceId && memory.getThreadById) {\n    const thread = await memory.getThreadById({ threadId });\n    if (!thread || thread.resourceId !== resourceId) {\n      throw new Error('Thread not found');\n    }\n  }\n\n  const MAX_PAGE = 50;\n  const MAX_LIMIT = 20;\n  const normalizedPage = Math.max(Math.min(page, MAX_PAGE), 1);\n  const normalizedLimit = Math.min(Math.max(limit, 1), MAX_LIMIT);\n  const pageIndex = normalizedPage - 1;\n  const fetchCount = pageIndex * normalizedLimit + normalizedLimit + 1;\n\n  const result = await memory.recall({\n    threadId,\n    resourceId,\n    page: 0,\n    perPage: fetchCount,\n    orderBy: { field: 'createdAt', direction: anchor === 'end' ? 'DESC' : 'ASC' },\n  });\n","sourceCodeStart":1128,"sourceCodeEnd":1164,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/tools/om-tools.ts#L1128-L1164","documentation":"When a resourceId is supplied, recallThreadFromStart() verifies thread ownership: it loads the thread via memory.getThreadById({ threadId }) and requires it to exist AND have thread.resourceId === resourceId. If the thread does not exist, or exists under a different resource (user), it throws the generic 'Thread not found' — deliberately vague to avoid leaking cross-resource data.","triggerScenarios":"Calling recallThreadFromStart with a threadId that was never created, was deleted, or belongs to a different resourceId; the ownership check at om-tools.ts:1143-1148 then throws.","commonSituations":"Multi-tenant apps where the wrong user's resourceId is bound to the run; listing thread IDs from an old resource and browsing them under a new one; storage cleared/threads pruned while clients hold stale IDs; typos in thread IDs; passing a resourceId that differs from the one used at thread creation.","solutions":["Confirm the threadId exists: const t = await memory.getThreadById({ threadId }); check t is non-null.","Ensure the resourceId passed at runtime matches the thread's resourceId at creation time.","Re-create the thread with memory.createThread({ threadId, resourceId }) if it was deleted.","In multi-tenant setups, derive resourceId from authenticated context, not client input; omit resourceId if cross-resource browsing is intended (the check only runs when resourceId is provided)."],"exampleFix":"// before\nawait recallThreadFromStart({ memory, threadId: input.threadId, resourceId: userA });\n// after\nconst thread = await memory.getThreadById({ threadId: input.threadId });\nif (!thread || thread.resourceId !== userA) {\n  return 'Thread not found for this user.';\n}\nawait recallThreadFromStart({ memory, threadId: input.threadId, resourceId: userA });","handlingStrategy":"try-catch","validationCode":"// pre-check ownership before browsing\nconst thread = await memory.getThreadById({ threadId });\nif (!thread) throw new Error(`Thread ${threadId} does not exist`);\nif (resourceId && thread.resourceId !== resourceId) {\n  throw new Error(`Thread ${threadId} belongs to resource ${thread.resourceId}`);\n}","typeGuard":"function isAccessibleThread(\n  thread: { resourceId: string } | null,\n  resourceId?: string\n): thread is { resourceId: string } {\n  return !!thread && (!resourceId || thread.resourceId === resourceId);\n}","tryCatchPattern":"try {\n  return await recallThreadFromStart({ memory, threadId, resourceId });\n} catch (err) {\n  if (err instanceof Error && err.message === 'Thread not found') {\n    // do not leak whether it was missing vs. forbidden\n    return { error: 'Thread not found or not accessible for this user.' };\n  }\n  throw err;\n}","preventionTips":["Derive resourceId from the authenticated session, never from client input.","List browsable threads via the resource-scope mode='threads' instead of guessing IDs.","Handle thread deletion (retention policies) by validating existence before deep links."],"tags":["memory","authorization","not-found","multi-tenancy"],"backgroundTag":"resource-not-found","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}