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
- 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 } }));
});
- 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(() => {});
}
- 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
Bug Description
WebSocketStreamoccasionally yields chunks fromopened.readablethat 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:
This causes
JSON.parse()to fail with:The issue reproduces against a local mock WebSocket server.
Reproducible By
WebSocketStreamclient loop that readsopened.readableand parses each chunk as JSON.Expected Behavior
opened.readableshould 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:
Also observed intermittently in the same short-lived connection workload:
Environment
Darwin 25.3.0 (arm64)v24.12.0undicipackage version:7.24.6(still exists on8.0.0)process.versions.undici:7.16.0Additional context