{"record":{"id":"a181563281c8d777","repo":"mastra-ai/mastra","slug":"messages-should-be-an-array","errorCode":null,"errorMessage":"Messages should be an array","messagePattern":"Messages should be an array","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/memory.ts","lineNumber":1305,"sourceCode":"  summary: 'Save messages',\n  description: 'Saves new messages to memory',\n  tags: ['Memory'],\n  requiresAuth: true,\n  handler: async ({ mastra, agentId, messages, requestContext }) => {\n    try {\n      const effectiveResourceId = getEffectiveResourceId(requestContext, undefined);\n      const memory = await getMemoryFromContext({ mastra, agentId, requestContext });\n\n      if (!memory) {\n        throw new HTTPException(400, { message: 'Memory is not initialized' });\n      }\n\n      if (!messages) {\n        throw new HTTPException(400, { message: 'Messages are required' });\n      }\n\n      if (!Array.isArray(messages)) {\n        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, {","sourceCodeStart":1287,"sourceCodeEnd":1323,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/memory.ts#L1287-L1323","documentation":"The POST /memory/save-messages handler requires the request body's `messages` field to be a JSON array. The permissive body schema accepts unknown shapes, so the handler explicitly checks `Array.isArray(messages)` and rejects non-array payloads with HTTP 400. This guards downstream code that iterates and normalizes each message.","triggerScenarios":"POST /api/memory/save-messages with a body where `messages` is a single object (not wrapped in an array), a string, a number, or null-adjacent non-array JSON (missing/undefined `messages` hits the separate 'Messages are required' 400 instead).","commonSituations":"Client SDKs or scripts posting one message object directly as `messages: {...}` instead of `messages: [{...}]`; hand-written curl/fetch calls; a custom client that mis-serializes a Map or single message; version drift where an older client sent a bare object that a looser endpoint once tolerated.","solutions":["Wrap the message in an array: send `messages: [message]` instead of `messages: message`.","Inspect the actual JSON body with a debugger or `JSON.stringify(body)` to confirm `messages` is serialized as an array, not an object or string.","If using @mastra/client-js, use the client's saveMessages method so the payload shape is built for you.","Add client-side serialization guards (e.g., `Array.isArray`) before calling the endpoint."],"exampleFix":"// before\nawait fetch('/api/memory/save-messages', { method: 'POST', body: JSON.stringify({ messages: { threadId: 't1', resourceId: 'r1', content: 'hi' } }) });\n// after\nawait fetch('/api/memory/save-messages', { method: 'POST', body: JSON.stringify({ messages: [{ threadId: 't1', resourceId: 'r1', content: 'hi' }] }) });","handlingStrategy":"validation","validationCode":"const body = { messages };\nif (!Array.isArray(body.messages)) {\n  throw new TypeError('save-messages: `messages` must be an array');\n}","typeGuard":"function isMessageArray(v: unknown): v is Array<Record<string, unknown>> {\n  return Array.isArray(v);\n}","tryCatchPattern":"try {\n  await saveMessages({ messages });\n} catch (e) {\n  if (isHttpError(e) && e.status === 400 && /should be an array/.test(e.message)) {\n    // fix payload shape: wrap single message in an array\n  } else throw e;\n}","preventionTips":["Always send `messages` as an array, even for a single message.","Use @mastra/client-js typed methods instead of hand-rolled fetch bodies.","Validate payloads with zod before sending.","Log the serialized body when debugging 400s."],"tags":["http-400","request-validation","memory","api"],"backgroundTag":"invalid-request-body-shape","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}