{"record":{"id":"84d86f818125736c","repo":"mastra-ai/mastra","slug":"messages-must-be-an-array-of-uimessage-objects","errorCode":null,"errorMessage":"Messages must be an array of UIMessage objects","messagePattern":"Messages must be an array of UIMessage objects","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client-sdks/ai-sdk/src/chat-route.ts","lineNumber":327,"sourceCode":"  // same or it would execute a stale/empty code-defined agent (issue #18574). An\n  // explicit agentVersion (from query params or route options) wins; otherwise we\n  // default to the published version, matching the built-in agent handlers.\n  let agentObj = baseAgent;\n  const editorAgent = mastra.getEditor?.()?.agent;\n  if (editorAgent) {\n    agentObj = await editorAgent.applyStoredOverrides(\n      baseAgent,\n      agentVersion ?? { status: 'published' },\n      requestContext as RequestContext | undefined,\n    );\n  } else if (agentVersion) {\n    // No editor configured: preserve the prior behavior of surfacing the\n    // \"editor required for versioned agent lookup\" error for explicit versions.\n    agentObj = await mastra.getAgentById(agentId, agentVersion);\n  }\n\n  if (!Array.isArray(messages)) {\n    throw new Error('Messages must be an array of UIMessage objects');\n  }\n\n  // Capture the last assistant message ID for the stream response.\n  // This helps the frontend identify which message the response corresponds to.\n  let lastMessageId: string | undefined;\n  let messagesToSend = messages;\n\n  if (messages.length > 0) {\n    const lastMessage = messages[messages.length - 1]!;\n    if (lastMessage?.role === 'assistant') {\n      lastMessageId = lastMessage.id;\n\n      // For regeneration, remove the last assistant message so the LLM generates fresh text\n      if (trigger === 'regenerate-message') {\n        messagesToSend = messages.slice(0, -1);\n      }\n    }\n  }","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/client-sdks/ai-sdk/src/chat-route.ts#L309-L345","documentation":"The chat route expects the request body's messages field to be an array of AI SDK UIMessage objects (the useChat wire format). If messages is missing or not an array, the handler cannot build the stream and throws before invoking the agent. This is an input-shape validation guard.","triggerScenarios":"POSTing a body where messages is a single object, a string, or absent; custom clients (curl, scripts) that send prompt-style payloads instead of the UIMessage array format.","commonSituations":"Calling the endpoint manually with { prompt: '...' }; migrating from v4-style { messages: [...] } with different message shapes; proxy layers that unwrap the array; frontend bug sending undefined messages before chat init.","solutions":["Send messages as an array of UIMessage objects: [{ id, role: 'user', parts: [{ type: 'text', text: '...' }] }]","Use the AI SDK client (useChat) which serializes messages correctly, instead of hand-built fetch bodies","Validate/normalize the body in a middleware before it reaches the handler","Log the incoming body to confirm the messages field survives any proxies/gateways"],"exampleFix":"// before\nfetch('/api/chat/my-agent', { method: 'POST', body: JSON.stringify({ prompt: 'hi' }) })\n// after\nfetch('/api/chat/my-agent', { method: 'POST', body: JSON.stringify({\n  messages: [{ id: 'm1', role: 'user', parts: [{ type: 'text', text: 'hi' }] }]\n}) })","handlingStrategy":"type-guard","validationCode":"function isUIMessageArray(v: unknown): v is Array<{ id: string; role: string; parts: unknown[] }> {\n  return Array.isArray(v) && v.every(m => m && typeof m === 'object' && 'role' in m && 'parts' in m);\n}\nif (!isUIMessageArray(body.messages)) throw new Error('messages must be a UIMessage[]');","typeGuard":"function isUIMessage(m: unknown): m is { id: string; role: 'user' | 'assistant' | 'system'; parts: unknown[] } {\n  return (\n    typeof m === 'object' && m !== null &&\n    typeof (m as any).id === 'string' &&\n    typeof (m as any).role === 'string' &&\n    Array.isArray((m as any).parts)\n  );\n}","tryCatchPattern":"try {\n  const stream = await handleChatStream({ ...params });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('must be an array of UIMessage')) {\n    return new Response(JSON.stringify({ error: 'INVALID_MESSAGES' }), { status: 400 });\n  }\n  throw err;\n}","preventionTips":["Use useChat from the AI SDK for the client so the body format is always correct","When testing with curl/scripts, copy the exact UIMessage shape ({ id, role, parts })","Validate the request body with a schema (zod) in middleware before the handler","Check for proxies/gateways that might rewrite or unwrap the messages field"],"tags":["ai-sdk","validation","request-body","schema-validation-failed"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}