{"id":"d74676a1dc8d1822","repo":"websockets/ws","slug":"unexpected-character-at-index-i-d74676","errorCode":null,"errorMessage":"Unexpected character at index ${i}","messagePattern":"Unexpected character at index (.+?)","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"warning","filePath":"lib/subprotocol.js","lineNumber":30,"sourceCode":"function parse(header) {\n  const protocols = new Set();\n  let start = -1;\n  let end = -1;\n  let i = 0;\n\n  for (i; i < header.length; i++) {\n    const code = header.charCodeAt(i);\n\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) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/websockets/ws/blob/ae1de54330cef77e487548890fabfeb9aae1d83d/lib/subprotocol.js#L12-L48","documentation":"Thrown by subprotocol.parse() while parsing the Sec-WebSocket-Protocol request header on the server. At lib/subprotocol.js:28-31, when a comma (0x2c) is encountered but start === -1 (no protocol token has begun), the parser throws SyntaxError indicating a comma appeared where a protocol name was expected. This is one of the malformed-header paths inside WebSocketServer.handleUpgrade() (websocket-server.js:286-292) where it is caught and converted into an HTTP 400 'Invalid Sec-WebSocket-Protocol header' response.","triggerScenarios":"A client sends a Sec-WebSocket-Protocol header that begins with a comma or contains consecutive commas, e.g. ',chat' or 'chat,,soap'. On the server side this is surfaced as a 400 handshake abort (or a 'wsClientError' event if a listener exists), not as a thrown error in user code. The raw throw is only seen if you call subprotocol.parse() directly.","commonSituations":"Client libraries that join an empty protocol list with commas; misconfigured proxies that prepend/append commas to header values; hand-crafted WebSocket clients with broken header construction.","solutions":["If consuming the error: listen for the 'wsClientError' event on WebSocketServer to log/inspect these clients, since by default they get a 400.","On the client side, build the Sec-WebSocket-Protocol header by joining a non-empty list of valid token strings with a single comma.","Sanitize/strip empty segments before setting the header if you proxy client requests."],"exampleFix":"// before (client building header)\nreq.setHeader('Sec-WebSocket-Protocol', ['', 'chat'].join(','));\n\n// after\nreq.setHeader('Sec-WebSocket-Protocol', ['chat'].join(','));","handlingStrategy":"try-catch","validationCode":"const subprotocol = require('ws/lib/subprotocol');\nfunction isValidProtocolHeader(header) {\n  if (typeof header !== 'string' || header.length === 0) return false;\n  try {\n    subprotocol.parse(header);\n    return true;\n  } catch {\n    return false;\n  }\n}","typeGuard":"function looksLikeProtocolHeader(header) {\n  return typeof header === 'string' && header.length > 0 && !header.startsWith(',');\n}","tryCatchPattern":"wss.on('wsClientError', (err, socket, req) => {\n  // default behavior (400) already applied; log/handle here\n  console.warn('bad handshake from', req.socket.remoteAddress, err.message);\n});","preventionTips":["On the server, attach a 'wsClientError' listener to observe malformed clients without crashes.","On the client, build Sec-WebSocket-Protocol from a non-empty array of valid tokens joined by single commas.","Strip empty segments introduced by proxies before forwarding the header."],"tags":["websocket","subprotocol","handshake","header-parsing"],"analyzedSha":"ae1de54330cef77e487548890fabfeb9aae1d83d","analyzedAt":"2026-08-03T19:11:18.437Z","schemaVersion":2}