websockets/ws · warning · SyntaxError
The " " subprotocol is duplicated
Error message
The "${protocol}" subprotocol is duplicated What it means
Thrown by subprotocol.parse() at subprotocol.js:37-38 when a protocol token delimited by commas has already been seen earlier in the same Sec-WebSocket-Protocol header. RFC 6455 §4.1 requires the client request at most one occurrence of each value; the server parser rejects duplicates using a Set.
Solutions
- Dedupe the protocol list on the client before connecting: [...new Set(protocols)].join(',').
- If using the ws client constructor, pass a de-duplicated array: new WebSocket(url, [...new Set(protocols)]).
- On the server, no action needed — handleUpgrade already catches and rejects with HTTP 400.
Example fix
// before const ws = new WebSocket(url, ['chat', 'chat', 'json']); // after const ws = new WebSocket(url, [...new Set(['chat', 'chat', 'json'])]);
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: dedupe before sending
function uniqueProtocols(protocols) {
return [...new Set(protocols.filter(p => typeof p === 'string' && p.length > 0))];
}
// usage: new WebSocket(url, uniqueProtocols(list)); Type guard
function hasNoDuplicateProtocols(protocols) {
return new Set(protocols).size === protocols.length;
} Try / catch
const { parse } = require('ws/lib/subprotocol');
try {
protocols = parse(header);
} catch (err) {
if (/duplicated/.test(err.message)) {
// client sent a duplicate protocol; reject
}
socket.destroy();
} Prevention
- Dedupe protocol lists with a Set before joining or passing to the constructor.
- On the server, handleUpgrade already rejects duplicate-laden headers with 400.
- Avoid concatenating protocol headers in proxies without deduplication.
When it happens
Trigger: A client sends Sec-WebSocket-Protocol: chat, chat or soap, mqtt, soap. When the parser hits the comma after the second 'chat', it finds 'chat' already in the protocols Set (subprotocol.js:37) and throws. On the server this is caught in handleUpgrade (websocket-server.js:286-292) and aborts the handshake with 400.
Common situations: A client library sends the default protocol plus a user-servised one that collide; a reverse proxy merges multiple client headers by concatenation creating duplicates; a misconfigured client passes the same protocol string twice in its array.
Related errors
- Unexpected character at index
- Unexpected end of input
- An invalid or duplicated subprotocol was specified
- First argument must be a valid error code number
- The data size must not be greater than 125 bytes
AI-assisted analysis of websockets/ws@c791e707ea (2026-08-06).
Data as JSON: /api/errors/488b4de7d0ddc989.
Report an issue: GitHub.
Appendix: source
Thrown at lib/subprotocol.js:38
if (end === -1 && tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (
i !== 0 &&
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
) {
if (end === -1 && start !== -1) end = i;
} else if (code === 0x2c /* ',' */) {
if (start === -1) {
throw new SyntaxError(`Unexpected character at index ${i}`);
}
if (end === -1) end = i;
const protocol = header.slice(start, end);
if (protocols.has(protocol)) {
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
}
protocols.add(protocol);
start = end = -1;
} else {
throw new SyntaxError(`Unexpected character at index ${i}`);
}
}
if (start === -1 || end !== -1) {
throw new SyntaxError('Unexpected end of input');
}
const protocol = header.slice(start, i);
if (protocols.has(protocol)) {
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
}View on GitHub (pinned to c791e707ea)