{"record":{"id":"b62093d6f7f94ec7","repo":"mastra-ai/mastra","slug":"thread-with-id-threadid-resourceid-does-not-mat","errorCode":null,"errorMessage":"Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}","messagePattern":"Thread with id (.+?) resourceId does not match the current resourceId (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/memory/mock.ts","lineNumber":281,"sourceCode":"            throw new Error('Thread ID is required for thread-scoped working memory updates');\n          }\n          if (scope === 'resource' && !resourceId) {\n            throw new Error('Resource ID is required for resource-scoped working memory updates');\n          }\n\n          if (threadId) {\n            let thread = await memory.getThreadById({ threadId });\n\n            if (!thread) {\n              thread = await memory.createThread({\n                threadId,\n                resourceId,\n                memoryConfig: _config,\n              });\n            }\n\n            if (thread.resourceId && resourceId && thread.resourceId !== resourceId) {\n              throw new Error(\n                `Thread with id ${threadId} resourceId does not match the current resourceId ${resourceId}`,\n              );\n            }\n          }\n\n          let workingMemory: string;\n\n          if (usesMergeSemantics) {\n            const existingRaw = await memory.getWorkingMemory({\n              threadId,\n              resourceId,\n              memoryConfig: _config,\n            });\n\n            let existingData: Record<string, unknown> | null = null;\n            if (existingRaw) {\n              try {\n                existingData = typeof existingRaw === 'string' ? JSON.parse(existingRaw) : existingRaw;","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/memory/mock.ts#L263-L299","documentation":"When updating working memory within a thread, the memory implementation verifies that the thread belongs to the given resourceId. If the stored thread's resourceId differs from the resourceId passed in the call, this error is thrown to prevent cross-tenant/cross-user memory contamination. It is a safety check, not an infrastructure failure.","triggerScenarios":"Calling memory.updateWorkingMemory({ threadId, resourceId, ... }) — or the working-memory tool path — where the thread exists, thread.resourceId is set, and thread.resourceId !== resourceId (e.g. thread 'abc' was created for user 'user-1' but the call passes 'user-2').","commonSituations":"Multi-tenant apps where a session/token maps to a different user than the one who created the thread; reusing cached thread IDs across users or workspaces; copying thread IDs in tests between fixtures; a bug where resourceId is read from the wrong request field.","solutions":["Verify the resourceId you pass matches the thread's owner: fetch the thread via memory.getThreadById({ threadId }) and check thread.resourceId","Look up the correct thread for the current resource instead of reusing a stale threadId (store threadId per resourceId, e.g. threadId = `${resourceId}-main`)","If the thread genuinely moved, create a new thread for the resource rather than reassigning (thread-resource binding is immutable by design)","Audit where resourceId originates (auth token vs request body) to ensure consistent values across calls"],"exampleFix":"// before\nawait memory.updateWorkingMemory({ threadId: 'thread-123', resourceId: currentUserId, workingMemory });\n// after\nconst thread = await memory.getThreadById({ threadId: 'thread-123' });\nif (thread.resourceId !== currentUserId) {\n  throw new Error('Thread belongs to a different resource');\n}\nawait memory.updateWorkingMemory({ threadId: 'thread-123', resourceId: thread.resourceId, workingMemory });","handlingStrategy":"validation","validationCode":"async function assertThreadOwnedByResource(memory: Memory, threadId: string, resourceId: string) {\n  const thread = await memory.getThreadById({ threadId });\n  if (!thread) throw new Error(`Thread ${threadId} not found`);\n  if (thread.resourceId && thread.resourceId !== resourceId) {\n    throw new Error(`Thread ${threadId} belongs to resource ${thread.resourceId}, not ${resourceId}`);\n  }\n  return thread;\n}","typeGuard":"const threadMatchesResource = (\n  thread: { resourceId?: string | null },\n  resourceId: string,\n): boolean => !thread.resourceId || thread.resourceId === resourceId;","tryCatchPattern":"try {\n  await memory.updateWorkingMemory({ threadId, resourceId, workingMemory });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('does not match the current resourceId')) {\n    // cross-resource access attempt: create/use a thread owned by this resource\n    const thread = await memory.createThread({ resourceId, title: 'main' });\n    await memory.updateWorkingMemory({ threadId: thread.id, resourceId, workingMemory });\n  } else throw err;\n}","preventionTips":["Key stored thread IDs by resourceId (e.g. a per-user 'main thread' mapping) and never reuse across users","Read resourceId from a single trusted source (auth token/session), not ad-hoc request fields","Before cross-user operations, fetch the thread and compare thread.resourceId","In multi-tenant tests, use distinct fixtures per resource to catch mismatches early","Treat thread-resource binding as immutable; create a new thread instead of reassigning"],"tags":["memory","tenant-mismatch","thread","validation"],"backgroundTag":"resource-ownership-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}