{"id":"488b4de7d0ddc989","repo":"websockets/ws","slug":"the-protocol-subprotocol-is-duplicated","errorCode":null,"errorMessage":"The \"${protocol}\" subprotocol is duplicated","messagePattern":"The \"(.+?)\" subprotocol is duplicated","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"warning","filePath":"lib/subprotocol.js","lineNumber":38,"sourceCode":"\n    if (end === -1 && tokenChars[code] === 1) {\n      if (start === -1) start = i;\n    } else if (\n      i !== 0 &&\n      (code === 0x20 /* ' ' */ || code === 0x09) /* '\\t' */\n    ) {\n      if (end === -1 && start !== -1) end = i;\n    } else if (code === 0x2c /* ',' */) {\n      if (start === -1) {\n        throw new SyntaxError(`Unexpected character at index ${i}`);\n      }\n\n      if (end === -1) end = i;\n\n      const protocol = header.slice(start, end);\n\n      if (protocols.has(protocol)) {\n        throw new SyntaxError(`The \"${protocol}\" subprotocol is duplicated`);\n      }\n\n      protocols.add(protocol);\n      start = end = -1;\n    } else {\n      throw new SyntaxError(`Unexpected character at index ${i}`);\n    }\n  }\n\n  if (start === -1 || end !== -1) {\n    throw new SyntaxError('Unexpected end of input');\n  }\n\n  const protocol = header.slice(start, i);\n\n  if (protocols.has(protocol)) {\n    throw new SyntaxError(`The \"${protocol}\" subprotocol is duplicated`);\n  }","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/websockets/ws/blob/ae1de54330cef77e487548890fabfeb9aae1d83d/lib/subprotocol.js#L20-L56","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Client SDKs that concatenate user protocols with defaults without deduplicating; browsers where two libraries both register their protocol; proxies that merge headers naively.","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."],"exampleFix":"// before\nconst protocols = ['chat', 'chat', 'soap'];\nreq.setHeader('Sec-WebSocket-Protocol', protocols.join(','));\n\n// after\nreq.setHeader('Sec-WebSocket-Protocol', [...new Set(protocols)].join(','));","handlingStrategy":"validation","validationCode":"function buildProtocolHeader(protocols) {\n  const seen = new Set();\n  const out = [];\n  for (const p of protocols) {\n    if (typeof p === 'string' && p.length && !seen.has(p)) {\n      seen.add(p);\n      out.push(p);\n    }\n  }\n  return out.join(',');\n}","typeGuard":"function isUniqueProtocolList(protocols) {\n  const seen = new Set();\n  for (const p of protocols) {\n    if (typeof p !== 'string' || seen.has(p)) return false;\n    seen.add(p);\n  }\n  return true;\n}","tryCatchPattern":"wss.on('wsClientError', (err, socket, req) => {\n  if (/duplicated/.test(err.message)) {\n    // client sent duplicate protocol; default 400 already sent\n  }\n});","preventionTips":["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."],"tags":["websocket","subprotocol","handshake","header-parsing"],"analyzedSha":"ae1de54330cef77e487548890fabfeb9aae1d83d","analyzedAt":"2026-08-03T19:11:18.437Z","schemaVersion":2}