{"record":{"id":"2eacd26411fc44c9","repo":"moeru-ai/airi","slug":"invalid-airi-websocket-message","errorCode":null,"errorMessage":"Invalid AIRI websocket message.","messagePattern":"Invalid AIRI websocket message\\.","errorType":"validation","errorClass":"InvalidMessageError","httpStatus":null,"severity":"error","filePath":"packages/server-sdk/src/codec.ts","lineNumber":58,"sourceCode":"/** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */\nexport function parseEvent<C = undefined>(text: string): WebSocketEvent<C> {\n  let superJsonParsed: WebSocketEvent<C> | undefined\n  let superJsonError: unknown\n\n  try {\n    superJsonParsed = parse<WebSocketEvent<C>>(text)\n  }\n  catch (error) {\n    superJsonError = error\n  }\n\n  const potentialEvent = superJsonParsed && typeof superJsonParsed === 'object' && 'type' in superJsonParsed\n    ? superJsonParsed\n    : parsePlainJson(text, superJsonError)\n\n  const result = safeParse(eventEnvelopeSchema, potentialEvent)\n  if (!result.success) {\n    throw new InvalidMessageError({ cause: result.issues, source: potentialEvent })\n  }\n\n  return potentialEvent as WebSocketEvent<C>\n}\n\n/** Serializes one AIRI websocket protocol event with SuperJSON. */\nexport function stringifyEvent<C = undefined>(\n  event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>,\n) {\n  return stringify(event)\n}\n\nfunction parsePlainJson(text: string, superJsonError: unknown): unknown {\n  try {\n    return JSON.parse(text)\n  }\n  catch (jsonError) {\n    throw new InvalidMessageError({","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/server-sdk/src/codec.ts#L40-L76","documentation":"This is the client-side decode path in server-sdk/src/codec.ts. Incoming text is first parsed with SuperJSON; if that yields an object with a `type` field it is used, otherwise parsePlainJson is tried. InvalidMessageError ('Invalid AIRI websocket message.') is thrown when the parsed object reached safeParse(eventEnvelopeSchema, ...) and failed, i.e. the text was parseable but is not a valid AIRI event envelope (missing/invalid `type`, wrong payload shape).","triggerScenarios":"The server (or a test double) sending plain objects without the envelope `type` field, events produced by an older/newer protocol version whose schema diverged, or SuperJSON payloads whose reconstructed types no longer match eventEnvelopeSchema.","commonSituations":"See trigger scenarios.","solutions":["Confirm the peer serializes with the matching stringifyEvent from the same protocol version","Read the valibot issues on the error's `cause` to find the offending envelope field","Pin server and client packages to compatible versions and redeploy together","When mocking a server in tests, emit events built from the shared event definitions instead of ad-hoc objects"],"exampleFix":"// before\nserver.clients.forEach(c => c.send(JSON.stringify({ ping: Date.now() })))\n// after\nimport { stringifyEvent } from '@proj-airi/server-runtime'\nserver.clients.forEach(c => c.send(stringifyEvent({ type: 'ping', payload: { at: Date.now() } })))","handlingStrategy":"try-catch","validationCode":"import { safeParse } from 'valibot'\nimport { eventEnvelopeSchema } from '@proj-airi/server-shared'\n\nconst check = safeParse(eventEnvelopeSchema, candidate)\nif (!check.success)\n  return { ok: false as const, issues: check.issues }","typeGuard":"function looksLikeWebSocketEvent(value: unknown): value is { type: string } {\n  return typeof value === 'object' && value !== null && 'type' in value\n    && typeof (value as { type: unknown }).type === 'string'\n}","tryCatchPattern":"try {\n  const event = parseEvent<C>(text)\n}\ncatch (error) {\n  if (error instanceof InvalidMessageError) {\n    logger.warn('ignoring non-conforming message', { issues: error.cause, source: error.source })\n    return\n  }\n  throw error\n}","preventionTips":["Generate test/server traffic with stringifyEvent from the same version","Treat protocol packages as one unit when upgrading the monorepo","Check error.cause issues first; they name the exact failing field"],"tags":["websocket","superjson","valibot","schema","server-sdk"],"backgroundTag":"websocket-event-schema-mismatch","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}