{"record":{"id":"d979f0e615fa34dc","repo":"mastra-ai/mastra","slug":"all-messages-for-the-same-threadid-must-use-the-sa","errorCode":null,"errorMessage":"All messages for the same threadId must use the same resourceId.","messagePattern":"All messages for the same threadId must use the same resourceId\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/memory.ts","lineNumber":1323,"sourceCode":"        throw new HTTPException(400, { message: 'Messages should be an array' });\n      }\n\n      // The body schema is intentionally permissive (unknown[]); narrow to the\n      // fields this handler validates and normalizes.\n      const incomingMessages = messages as Array<\n        { id?: string; threadId?: string; resourceId?: string; createdAt?: string | Date } & Record<string, unknown>\n      >;\n\n      const resourceIdByThread = new Map<string, string>();\n      for (const message of incomingMessages) {\n        if (!message.threadId || !message.resourceId) {\n          continue;\n        }\n        const existingResourceId = resourceIdByThread.get(message.threadId);\n        if (!existingResourceId) {\n          resourceIdByThread.set(message.threadId, message.resourceId);\n        } else if (existingResourceId !== message.resourceId) {\n          throw new HTTPException(400, {\n            message: 'All messages for the same threadId must use the same resourceId.',\n          });\n        }\n      }\n\n      // Validate that all messages have threadId and resourceId\n      const invalidMessages = incomingMessages.filter(message => !message.threadId || !message.resourceId);\n      if (invalidMessages.length > 0) {\n        throw new HTTPException(400, {\n          message: `All messages must have threadId and resourceId fields. Found ${invalidMessages.length} invalid message(s).`,\n        });\n      }\n\n      // If effectiveResourceId is set, validate all messages belong to this resource\n      if (effectiveResourceId) {\n        const unauthorizedMessages = incomingMessages.filter(message => message.resourceId !== effectiveResourceId);\n        if (unauthorizedMessages.length > 0) {\n          throw new HTTPException(403, {","sourceCodeStart":1305,"sourceCodeEnd":1341,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/memory.ts#L1305-L1341","documentation":"Within a single save-messages call, every message carrying the same threadId must also carry the same resourceId. The handler builds a threadId→resourceId map and throws HTTP 400 as soon as a second message reuses a threadId with a different resourceId, preventing a thread from being split across resources.","triggerScenarios":"POST /memory/save-messages with a batch like [{threadId:'t1', resourceId:'r1', ...}, {threadId:'t1', resourceId:'r2', ...}] — same threadId appears with two distinct resourceIds in one payload.","commonSituations":"Batch-saving conversation history after a user/resource migration where some messages still carry the old resourceId; copying messages between resources without rewriting threadIds; concurrent writers with stale resourceId caches assembling a combined batch.","solutions":["Ensure each message in the batch uses the resourceId that owns its threadId; remap before sending.","Split the batch into separate requests per (threadId, resourceId) pair if the data genuinely spans resources.","If a resource migration caused this, first update the thread's resourceId (PATCH /memory/threads/:threadId) or reassign all messages consistently.","Deduplicate/reconcile client-side by grouping messages by threadId and verifying one resourceId per group."],"exampleFix":"// before\nmessages = [{ threadId: 't1', resourceId: 'user-a', ... }, { threadId: 't1', resourceId: 'user-b', ... }];\n// after\nmessages = messages.map(m => ({ ...m, resourceId: 'user-a' })); // normalize batch to a single resourceId per thread","handlingStrategy":"validation","validationCode":"const byThread = new Map<string, string>();\nfor (const m of messages) {\n  const prev = byThread.get(m.threadId);\n  if (prev && prev !== m.resourceId) {\n    throw new Error(`threadId ${m.threadId} maps to multiple resourceIds: ${prev}, ${m.resourceId}`);\n  }\n  byThread.set(m.threadId, m.resourceId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await saveMessages({ messages });\n} catch (e) {\n  if (isHttpError(e) && e.status === 400 && e.message.includes('same resourceId')) {\n    // regroup batch: one request per (threadId, resourceId) pair\n  } else throw e;\n}","preventionTips":["Group messages by threadId and assert one resourceId per group before batching.","After resource migrations, rewrite threadId/resourceId pairs consistently.","Avoid mixing messages from different sources into one save batch.","Keep a single source of truth for the thread→resource mapping client-side."],"tags":["http-400","consistency","memory","batch-validation"],"backgroundTag":"inconsistent-resource-id","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}