{"record":{"id":"6a3d98927ebfec78","repo":"moeru-ai/airi","slug":"chat-ws-ws-error-event","errorCode":null,"errorMessage":"[chat-ws] ws error event:","messagePattern":"\\[chat-ws\\] ws error event:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/libs/chat-sync/ws-client.ts","lineNumber":361,"sourceCode":"      // retrying. When the server rejects auth, the only structured\n      // signal we get is the close `code` (the close `reason` body is\n      // also delivered but not used for routing here). 4001 is our\n      // protocol signal for \"this token will never succeed without\n      // rotation\"; calling `ws.close()` here sets\n      // useWebSocket's internal `explicitlyClosed` flag so the next\n      // onclose path skips the reconnect schedule. A token change below\n      // closes the old context and starts a new connection that authenticates\n      // with the new token after opening.\n      if (restartForNewToken && enabled.value && tokenRef.value) {\n        ws.open()\n      }\n      else if (ev.code === WS_CLOSE_UNAUTHORIZED) {\n        console.warn('[chat-ws] server rejected auth (4001), pausing reconnect until token rotates')\n        ws.close()\n      }\n    },\n    onError(_rawWs, event) {\n      console.warn('[chat-ws] ws error event:', event)\n    },\n  })\n\n  // Translate VueUse's 3-state status into our 4-state machine and fan it\n  // out to the orchestrator. The chat store creates this inside a Pinia\n  // setup, which gives us a parent effect scope for `watch` to attach to.\n  // NOTICE:\n  // We intentionally do NOT stop this watcher in `disconnect()`; previous\n  // behavior killed it permanently and any caller that did `disconnect()`\n  // followed by `connect()` silently stopped receiving status events. The\n  // watcher is idle while the socket is closed, so leaving it attached\n  // costs nothing. Use `destroy()` for terminal cleanup.\n  const stopStatusWatch = watch(\n    [ws.status, enabled, authenticated],\n    ([rawStatus, isEnabled, isAuthenticated]) => notifyStatus(mapStatus(rawStatus, isEnabled, isAuthenticated)),\n    { immediate: true },\n  )\n","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/moeru-ai/airi/blob/b6d0809ecb6421d492c4ce38aa7a76146c83269d/packages/stage-ui/src/libs/chat-sync/ws-client.ts#L343-L379","documentation":"VueUse useWebSocket's onError callback fired: the underlying browser WebSocket emitted an 'error' event. DOM WebSocket error events have almost no payload (no message/code), which is why the log prints the bare event. It precedes a close event; the code in onClose handles 4001 (unauthorized) by pausing reconnect until the token rotates. This log is transport-level noise unless the socket cannot reconnect at all.","triggerScenarios":"Connection attempt to the chat-sync endpoint fails (refused/DNS/TLS), or an established socket aborts (network drop, server crash, proxy timeout). The 4001 path is handled separately in onClose, so this handler mainly covers non-auth transport faults.","commonSituations":"Dev server restarted while the tab stayed open; laptop sleep/resume dropping the socket; flaky Wi-Fi; load balancer idle-timeout killing wss connections; wrong URL scheme (wss against a plain-http port).","solutions":["Confirm the endpoint and scheme: wss:// for https origins, ws:// only for localhost dev.","Keep idle connections alive or lower proxy/LB idle timeouts so the socket is not aborted mid-session.","For auth failures, ensure the token in urlRef is fresh — the client intentionally stops reconnecting after close code 4001 until token rotation reopens it.","Treat recurring pairs of this log with close code 1006 as a transport/network issue to fix server-side or in proxy config."],"exampleFix":"// before\nonError(_rawWs, event) => {\n  console.warn('[chat-ws] ws error event:', event)\n}\n\n// after — include readyState and URL context since the event itself is opaque\nonError(_rawWs, event) {\n  console.warn('[chat-ws] ws error event:', event?.type, 'readyState:', _rawWs?.readyState, 'url:', urlRef.value)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// onError is informational; act on close codes instead\nonClose(_ws, ev) {\n  if (ev.code === WS_CLOSE_UNAUTHORIZED) {\n    // rotate token; watcher on urlRef reopens the socket\n  }\n}","preventionTips":["Match ws/wss scheme to the page origin.","Configure proxies/load balancers with WebSocket upgrade support and adequate idle timeouts.","Refresh auth tokens before expiry so the 4001 path never triggers."],"tags":["websocket","network","vueuse","chat-sync"],"backgroundTag":"websocket-connection-error","analyzedSha":"b6d0809ecb6421d492c4ce38aa7a76146c83269d","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}