moeru-ai/airi · warning
[chat-ws] dropped malformed newMessages payload:
Error message
[chat-ws] dropped malformed newMessages payload:
What it means
The chat-sync WebSocket client validates every server 'newMessages' push against NewMessagesPayloadSchema with Valibot's safeParse before fanning out. On failure it logs the first issue's message, drops that one payload, and returns — one malformed server push cannot corrupt every subscriber or mergeCloudMessagesIntoSession. This is a trust-boundary guard: the payload never reaches handlers.
Source
Thrown at packages/stage-ui/src/libs/chat-sync/ws-client.ts:264
function disposeContext() {
while (contextDisposers.length > 0) {
const dispose = contextDisposers.pop()!
try {
dispose()
}
catch {}
}
context.value = undefined
}
function attachContextListeners(ctx: WsEventContext) {
contextDisposers.push(ctx.on(newMessages, (event) => {
// External boundary: validate the wire payload before fanning it out.
// A malformed server push would otherwise flow unchecked into every
// subscriber and into `mergeCloudMessagesIntoSession`.
const result = v.safeParse(NewMessagesPayloadSchema, event.body)
if (!result.success) {
console.warn('[chat-ws] dropped malformed newMessages payload:', result.issues[0]?.message)
return
}
const payload = result.output
for (const handler of newMessagesHandlers) {
try {
handler(payload)
}
catch (err) {
// Same isolation principle as notifyStatus: one bad listener should
// not silently drop messages for the rest.
console.warn('[chat-ws] newMessages handler threw:', errorMessageFrom(err))
}
}
}))
contextDisposers.push(ctx.on(wsErrorEvent, (event) => {
console.warn('[chat-ws] socket error:', event.body)
}))View on GitHub (pinned to b6d0809ecb)
Solutions
- Read result.issues[0].message in the warn and compare the received shape (log event.body temporarily) against NewMessagesPayloadSchema.
- Align server payload generation and the Valibot schema in the same release; use optional()/nullish for fields the server may omit.
- If a field is newly added, make the schema additive (optional first, required later) so old/new clients coexist.
- Note the payload is dropped, not queued — fetch a resync from the server after fixing.
Example fix
// before const name: string = v.string() // server sends null on deleted senders // after const name: string = v.nullable(v.string()) // or v.optional with a fallback
Defensive patterns
Strategy: validation
Validate before calling
const result = v.safeParse(NewMessagesPayloadSchema, event.body)
if (!result.success) {
// drop and optionally request a server resync
return
} Type guard
function isNewMessagesPayload(v: unknown): boolean {
return v.safeParse(NewMessagesPayloadSchema, v).success
} Prevention
- Keep wire schemas in a shared package so server and client compile against one definition.
- Make schema changes additive (optional fields first) during rolling deploys.
- Pair validation drops with a resync fetch so no messages are permanently lost.
When it happens
Trigger: Server and client schema drift (server adds/renames a field, changes a type, sends null for a required field); a server bug serializing Dates/undefined oddly; a proxy or serialization layer mangling the JSON body.
Common situations: Deploying client and server from different commits during a rolling release; changing the message wire contract without a versioned migration; server writing camelCase vs snake_case drift.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid WebSocket event format.
- Invalid AIRI websocket message.
- [chat-ws] socket error:
- [chat-ws] ws error event:
- [chat-sync] pullMessages failed for
AI-assisted analysis of moeru-ai/airi@b6d0809ecb (2026-08-18).
Data as JSON: /api/errors/e9ccf17e749d6c90.
Report an issue: GitHub.