mastra-ai/mastra · error · HTTPException
Messages should be an array
Error message
Messages should be an array
What it means
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.
Source
Thrown at packages/server/src/server/handlers/memory.ts:1305
summary: 'Save messages',
description: 'Saves new messages to memory',
tags: ['Memory'],
requiresAuth: true,
handler: async ({ mastra, agentId, messages, requestContext }) => {
try {
const effectiveResourceId = getEffectiveResourceId(requestContext, undefined);
const memory = await getMemoryFromContext({ mastra, agentId, requestContext });
if (!memory) {
throw new HTTPException(400, { message: 'Memory is not initialized' });
}
if (!messages) {
throw new HTTPException(400, { message: 'Messages are required' });
}
if (!Array.isArray(messages)) {
throw new HTTPException(400, { message: 'Messages should be an array' });
}
// The body schema is intentionally permissive (unknown[]); narrow to the
// fields this handler validates and normalizes.
const incomingMessages = messages as Array<
{ id?: string; threadId?: string; resourceId?: string; createdAt?: string | Date } & Record<string, unknown>
>;
const resourceIdByThread = new Map<string, string>();
for (const message of incomingMessages) {
if (!message.threadId || !message.resourceId) {
continue;
}
const existingResourceId = resourceIdByThread.get(message.threadId);
if (!existingResourceId) {
resourceIdByThread.set(message.threadId, message.resourceId);
} else if (existingResourceId !== message.resourceId) {
throw new HTTPException(400, {View on GitHub (pinned to 75dd419e61)
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.
Example fix
// before
await fetch('/api/memory/save-messages', { method: 'POST', body: JSON.stringify({ messages: { threadId: 't1', resourceId: 'r1', content: 'hi' } }) });
// after
await fetch('/api/memory/save-messages', { method: 'POST', body: JSON.stringify({ messages: [{ threadId: 't1', resourceId: 'r1', content: 'hi' }] }) }); Defensive patterns
Strategy: validation
Validate before calling
const body = { messages };
if (!Array.isArray(body.messages)) {
throw new TypeError('save-messages: `messages` must be an array');
} Type guard
function isMessageArray(v: unknown): v is Array<Record<string, unknown>> {
return Array.isArray(v);
} Try / catch
try {
await saveMessages({ messages });
} catch (e) {
if (isHttpError(e) && e.status === 400 && /should be an array/.test(e.message)) {
// fix payload shape: wrap single message in an array
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- All messages must have threadId and resourceId fields. Found
- Both threadId or resourceId must be provided
- Tool call id is required
- Agent "${agent.id}" does not have memory configured
- Memory storage is not configured for agent "${agent.id}"
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a181563281c8d777.
Report an issue: GitHub.