websockets/ws · warning · SyntaxError
The "${protocol}" subprotocol is duplicated
Error message
The "${protocol}" subprotocol is duplicated What it means
Thrown by subprotocol.parse() when a duplicate protocol name is found in the comma-separated list before a separating comma (lib/subprotocol.js:35-39). The parser uses a Set and rejects duplicates per RFC 6455, which expects at most one occurrence of each offered protocol. As with the other parse errors, on a live server this is caught in handleUpgrade() and turned into an HTTP 400 'Invalid Sec-WebSocket-Protocol header'.
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 ae1de54330)
Solutions
- Deduplicate the protocol list on the client before sending: [...new Set(protocols)].join(',').
- If you control the server and want leniency, handle the 'wsClientError' event and decide whether to proceed.
- Audit middleware/proxies that rewrite or concatenate Sec-WebSocket-Protocol.
Example fix
// before
const protocols = ['chat', 'chat', 'soap'];
req.setHeader('Sec-WebSocket-Protocol', protocols.join(','));
// after
req.setHeader('Sec-WebSocket-Protocol', [...new Set(protocols)].join(',')); Defensive patterns
Strategy: validation
Validate before calling
function buildProtocolHeader(protocols) {
const seen = new Set();
const out = [];
for (const p of protocols) {
if (typeof p === 'string' && p.length && !seen.has(p)) {
seen.add(p);
out.push(p);
}
}
return out.join(',');
} Type guard
function isUniqueProtocolList(protocols) {
const seen = new Set();
for (const p of protocols) {
if (typeof p !== 'string' || seen.has(p)) return false;
seen.add(p);
}
return true;
} Try / catch
wss.on('wsClientError', (err, socket, req) => {
if (/duplicated/.test(err.message)) {
// client sent duplicate protocol; default 400 already sent
}
}); Prevention
- Deduplicate the protocol list before sending the header.
- Centralize protocol-list construction so two modules cannot both append the same name.
- Inspect duplicates via 'wsClientError' rather than letting them surprise you.
When it happens
Trigger: A client sends Sec-WebSocket-Protocol: chat,chat,soap (duplicate before the final token). The server-side path surfaces as a 400 / 'wsClientError'. The throw is only directly visible if you call require('./lib/subprotocol').parse() yourself.
Common situations: Client SDKs that concatenate user protocols with defaults without deduplicating; browsers where two libraries both register their protocol; proxies that merge headers naively.
Related errors
- Unexpected character at index ${i}
- Unexpected end of input
- Unexpected character at index ${i}
- Unexpected end of input
- None of the extension offers can be accepted
AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03).
Data as JSON: /data/errors/488b4de7d0ddc989.json.
Report an issue: GitHub.