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

  1. Wrap the message in an array: send `messages: [message]` instead of `messages: message`.
  2. 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.
  3. If using @mastra/client-js, use the client's saveMessages method so the payload shape is built for you.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/a181563281c8d777. Report an issue: GitHub.