Skip to content

WebSocketStream sometimes mixes non-message bytes into opened.readable #4958

@colinaaa

Description

@colinaaa

Bug Description

WebSocketStream occasionally yields chunks from opened.readable that are not WebSocket message payloads.

In our case, the server sends JSON text frames, but the client sometimes receives chunks prefixed with unexpected bytes (looks like raw frame/socket data), for example:

�"{"event":"Initialize","data":1010}

This causes JSON.parse() to fail with:

SyntaxError: Unexpected token '�'

The issue reproduces against a local mock WebSocket server.

Reproducible By

  1. Start a local WebSocket server that sends two JSON text messages immediately after each connection.
// bench/mock-ws-server.ts
import { WebSocketServer } from "ws";

const port = 19876;
const wss = new WebSocketServer({
  port,
  path: "/",
  perMessageDeflate: false,
});

let nextId = 1000;

wss.on("connection", (socket) => {
  const id = nextId++;
  socket.send(JSON.stringify({ event: "Initialize", data: id }));
  socket.send(JSON.stringify({ event: "Ready", data: { id } }));
});
  1. Run a WebSocketStream client loop that reads opened.readable and parses each chunk as JSON.
// bench/repro-undici-wss.ts
import { WebSocketStream } from "undici";

const WS_URL = "ws://localhost:19876/";

for (let i = 1; i <= 500; i++) {
  const wss = new WebSocketStream(WS_URL);
  const { readable } = await wss.opened;
  const reader = readable.getReader();

  for (let j = 0; j < 2; j++) {
    const next = await reader.read();
    if (next.done) break;

    const raw = String(next.value);
    JSON.parse(raw);
  }

  reader.releaseLock();
  wss.close();
  await wss.closed.catch(() => {});
}
  1. Execute:
node bench/mock-ws-server.ts
node bench/repro-undici-wss.ts

Expected Behavior

opened.readable should yield complete message payloads only. For text frames, each chunk should be valid text payload data and parse as expected.

Logs & Screenshots

Observed output from reproduction:

[run 11] malformed chunk detected
prefix="�\"{\"event\":\"Initialize\",\"data\":1010}"
codes=65533,34,123,34,101,118,101,110,116,34,58,34

Also observed intermittently in the same short-lived connection workload:

TypeError [ERR_INVALID_STATE]: Invalid state: Controller is already closed
at .../websocketstream.js:395:38

Environment

  • OS: Darwin 25.3.0 (arm64)
  • Node: v24.12.0
  • undici package version: 7.24.6 (still exists on 8.0.0)
  • process.versions.undici: 7.16.0

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions