nocobase/nocobase · error · ResourceActionError

messages must be an array

Error message

messages must be an array

What it means

sendMessages validates that the incoming messages field is an array before reading message roles. A 400 ResourceActionError is thrown when messages is missing, null, or any non-array value (string, object, number). This guards downstream code that calls messages.find and iterates the list.

Source

Thrown at packages/plugins/@nocobase/plugin-ai/src/server/resource/aiConversations.ts:371

        messages,
        editingMessageId,
        model,
        webSearch,
        stream = true,
      } = ctx.action.params.values || {};

      const shouldStream = stream !== false;
      if (shouldStream) {
        setupSSEHeaders(ctx);
      }

      try {
        if (!sessionId) {
          throw new ResourceActionError(400, ctx.t('sessionId is required'));
        }

        if (!Array.isArray(messages)) {
          throw new ResourceActionError(400, ctx.t('messages must be an array'));
        }
        normalizeIncomingMessageAttachments(ctx, messages);
        const userMessage = messages.find((message: any) => message.role === 'user');
        if (!userMessage) {
          throw new ResourceActionError(400, ctx.t('user message is required'));
        }

        const conversation = await plugin.aiConversationsManager.getConversation({
          sessionId,
          userId,
        });

        if (!conversation) {
          throw new ResourceActionError(400, ctx.t('conversation not found'));
        }

        const employee = await getAIEmployee(ctx, employeeName);
        if (!employee) {

View on GitHub (pinned to fa42722fef)

Solutions

  1. Wrap the message payload in an array: messages: [{ role: 'user', content: ... }]
  2. Verify the request key is exactly 'messages' (not 'message') and is included in the serialized body
  3. If a single message, convert: const msgs = Array.isArray(m) ? m : [m]
  4. Check content-type is application/json so the body parses into an array rather than undefined

Example fix

// before
sendMessages({ values: { sessionId, messages: { role: 'user', content: { type: 'text', content: 'hi' } } } })
// after
sendMessages({ values: { sessionId, messages: [{ role: 'user', content: { type: 'text', content: 'hi' } }] } })
Defensive patterns

Strategy: validation

Validate before calling

function assertMessagesArray(messages) {
  if (!Array.isArray(messages)) throw new TypeError('messages must be an array of message objects');
  if (messages.length === 0) throw new TypeError('messages must not be empty');
}

Type guard

function isMessageArray(v) {
  return Array.isArray(v) && v.every((m) => m !== null && typeof m === 'object' && typeof m.role === 'string');
}

Try / catch

try {
  await sendMessages({ values: { sessionId, messages } });
} catch (err) {
  if (err?.status === 400 && /messages must be an array/.test(err.message ?? '')) {
    return sendMessages({ values: { sessionId, messages: [messages] } }); // wrap single object
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling sendMessages with values.messages undefined, with messages as a single object instead of an array (e.g. { messages: { role: 'user', ... } }), or with a JSON body where messages was serialized as null.

Common situations: Client accidentally passes one message object rather than wrapping it in an array; an SDK/HTTP layer drops the field because of a wrong content-type so it deserializes to undefined; refactored client sends 'message' (singular) key instead of 'messages'.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/f9f710705a702d10. Report an issue: GitHub.