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

  1. Deduplicate the protocol list on the client before sending: [...new Set(protocols)].join(',').
  2. If you control the server and want leniency, handle the 'wsClientError' event and decide whether to proceed.
  3. 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

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


AI-assisted analysis of websockets/ws@ae1de54330 (2026-08-03). Data as JSON: /data/errors/488b4de7d0ddc989.json. Report an issue: GitHub.