Problem
The guest path of POST /api/chat passes the client-supplied messages array to the stream pipeline without checking the shape of its elements. When a client sends a malformed element, the turn throws a TypeError during message preparation, before any model call, and the request is recorded as a failed turn (root research span at ERROR, statusMessage "unknown: We could not generate a response. Please try again.") instead of being rejected as a bad request.
The web UI always sends well-formed messages, so this is only reachable from non-UI clients. The impact is that malformed input is reported as a server-side stream failure, which mixes it into the same error class that real turn failures use.
Evidence
- Failing spans carry
streamErrorPhase: preparation, streamErrorStage: transform-messages, and streamErrorShape.name: TypeError, with no child observations and near-zero latency. The same shape has recurred on separate days, only on guest requests, with no chatId and no user message input.
transform-messages is the initial stage on the guest path (lib/streaming/create-ephemeral-chat-stream-response.ts), so the throw happens inside the preparation block: assignDataPartNonces -> stripSpecFromMessages -> compactHistoricalMessages / capHistoricalAttachments -> dedupeAttachments -> summarizeCarriedContext.
app/api/chat/route.ts only checks Array.isArray(messages). createEphemeralChatStreamResponse only rejects an empty array. Nothing checks that each element is an object with a string role and an array parts of objects with a string type. For example, assignDataPartNonces (lib/streaming/helpers/data-part-nonce.ts) reads message.parts?.some(part => ... part.type), which throws on a null message, a non-array parts, or a null part.
To reproduce locally as a guest: send POST /api/chat with messages: [null] or messages: [{ "role": "user", "parts": "hello" }].
Proposed direction
Validate the guest messages payload at the route boundary (or at the top of createEphemeralChatStreamResponse) and return 400 Bad Request for elements that are not UI messages with an array of typed parts. Keep the check structural and small; it only needs to guarantee the invariants the preparation helpers already assume.
Acceptance criteria
- A guest request with a malformed
messages element returns 400 and does not create an ERROR root span.
- Well-formed guest requests are unaffected.
- A unit test covers the rejected shapes above.
Problem
The guest path of
POST /api/chatpasses the client-suppliedmessagesarray to the stream pipeline without checking the shape of its elements. When a client sends a malformed element, the turn throws aTypeErrorduring message preparation, before any model call, and the request is recorded as a failed turn (rootresearchspan atERROR,statusMessage"unknown: We could not generate a response. Please try again.") instead of being rejected as a bad request.The web UI always sends well-formed messages, so this is only reachable from non-UI clients. The impact is that malformed input is reported as a server-side stream failure, which mixes it into the same error class that real turn failures use.
Evidence
streamErrorPhase: preparation,streamErrorStage: transform-messages, andstreamErrorShape.name: TypeError, with no child observations and near-zero latency. The same shape has recurred on separate days, only on guest requests, with nochatIdand no user message input.transform-messagesis the initial stage on the guest path (lib/streaming/create-ephemeral-chat-stream-response.ts), so the throw happens inside the preparation block:assignDataPartNonces->stripSpecFromMessages->compactHistoricalMessages/capHistoricalAttachments->dedupeAttachments->summarizeCarriedContext.app/api/chat/route.tsonly checksArray.isArray(messages).createEphemeralChatStreamResponseonly rejects an empty array. Nothing checks that each element is an object with a stringroleand an arraypartsof objects with a stringtype. For example,assignDataPartNonces(lib/streaming/helpers/data-part-nonce.ts) readsmessage.parts?.some(part => ... part.type), which throws on anullmessage, a non-arrayparts, or anullpart.To reproduce locally as a guest: send
POST /api/chatwithmessages: [null]ormessages: [{ "role": "user", "parts": "hello" }].Proposed direction
Validate the guest
messagespayload at the route boundary (or at the top ofcreateEphemeralChatStreamResponse) and return400 Bad Requestfor elements that are not UI messages with an array of typed parts. Keep the check structural and small; it only needs to guarantee the invariants the preparation helpers already assume.Acceptance criteria
messageselement returns 400 and does not create anERRORroot span.