Skip to content

Vite plugin crashes with unhandled ECONNRESET when WebSocket client disconnects abruptly #12047

@schickling

Description

@schickling

Summary

The @cloudflare/vite-plugin crashes with an unhandled socket error when a WebSocket client disconnects abruptly during active communication.

Error

node:events:486
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at TCP.onStreamRead (node:internal/stream_base_commons:216:20)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:89:21) {
  errno: -54,
  code: 'ECONNRESET',
  syscall: 'read'
}

Root Cause

The crash happens in the WebSocket handling code in @cloudflare/vite-plugin. The coupleWebSocket function from miniflare couples a Node.js WebSocket to a workerd WebSocket, but when the connection is abruptly closed, the socket emits an 'error' event that isn't handled.

From @cloudflare/vite-plugin/dist/index.mjs:

import { coupleWebSocket } from 'miniflare';

// In handleWebSocket function:
httpServer.on('upgrade', async (request, socket, head) => {
    // ...
    nodeWebSocket.handleUpgrade(request, socket, head, async (clientWebSocket) => {
        coupleWebSocket(clientWebSocket, workerWebSocket);  // No error handling
        nodeWebSocket.emit('connection', clientWebSocket, request);
    });
});

Neither the socket nor clientWebSocket has an error handler attached, so when ECONNRESET occurs, Node.js throws.

Reproduction

Minimal repro repo: https://github.com/schickling-repros/livestore-sync-crash-repro

git clone https://github.com/schickling-repros/livestore-sync-crash-repro
cd livestore-sync-crash-repro
npm install

# Terminal 1: Start Vite (with Durable Object sync backend)
npm run dev

# Terminal 2: Run stress test (rapidly connects/disconnects WebSockets)
npm run stress

The stress test creates many WebSocket connections to a Durable Object, syncs briefly, then closes. The crash is non-deterministic but usually happens within 50-200 connections.

# Increase iterations if needed
TOTAL_STORES=500 SYNC_WAIT_MS=20 npm run stress

Expected Behavior

The Vite dev server should gracefully handle client disconnections (ECONNRESET) without crashing. The error should be caught and logged, not thrown as an unhandled exception.

Suggested Fix

Add error handlers to the sockets in either:

  1. coupleWebSocket in miniflare - handle errors on the coupled sockets
  2. handleWebSocket in vite-plugin - add .on('error', ...) to socket/clientWebSocket

Example:

socket.on('error', (err) => {
    if (err.code === 'ECONNRESET') {
        // Client disconnected, clean up gracefully
        return;
    }
    console.error('WebSocket error:', err);
});

Environment

  • @cloudflare/vite-plugin: ^1.1.0
  • miniflare: (bundled with vite-plugin)
  • vite: ^6.3.3
  • Node.js: v24.12.0
  • macOS

Context

This was discovered while using LiveStore's sync backend (Durable Objects) with rapid store creation/teardown during a backfill operation. Originally filed as livestorejs/livestore#982 before identifying miniflare as the source.

Metadata

Metadata

Labels

vite-pluginRelating to the `@cloudflare/vite-plugin` package

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions