{"record":{"id":"2b1e2e62913a24b0","repo":"denoland/deno","slug":"protocol-options-protocol-not-in-the-request","errorCode":null,"errorMessage":"Protocol '${options.protocol}' not in the request's protocol list (non negotiable)","messagePattern":"Protocol '(.+?)' not in the request's protocol list \\(non negotiable\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/02_websocket.ts","lineNumber":86,"sourceCode":"  const accept = op_http_websocket_accept_header(websocketKey);\n\n  const r = newInnerResponse(101);\n  r.headerList = [\n    [\"upgrade\", \"websocket\"],\n    [\"connection\", \"Upgrade\"],\n    [\"sec-websocket-accept\", accept],\n  ];\n\n  const protocolsStr = request.headers.get(\"sec-websocket-protocol\") || \"\";\n  const protocols = StringPrototypeSplit(protocolsStr, \", \");\n  if (protocols && options.protocol) {\n    if (ArrayPrototypeIncludes(protocols, options.protocol)) {\n      ArrayPrototypePush(r.headerList, [\n        \"sec-websocket-protocol\",\n        options.protocol,\n      ]);\n    } else {\n      throw new TypeError(\n        `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`,\n      );\n    }\n  }\n\n  const {\n    _eventLoop,\n    _idleTimeoutDuration,\n    _idleTimeoutTimeout,\n    _readyState,\n    _rid,\n    _role,\n    _serverHandleIdleTimeout,\n    createWebSocketBranded,\n    installServerInspector,\n    SERVER,\n    WebSocket,\n  } = loadWebSocket();","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/02_websocket.ts#L68-L104","documentation":"Deno.upgradeWebSocket(request, { protocol }) lets the server pick one subprotocol, but only from the list the client offered in 'sec-websocket-protocol'. If options.protocol is not included (exact, case-sensitive ArrayPrototypeIncludes over the comma-split list), Deno refuses rather than silently negotiating a protocol the client did not ask for ('non negotiable').","triggerScenarios":"Server hardcodes protocol: 'v2.api' while client sent Sec-WebSocket-Protocol: v1.api or omitted the header; case mismatch ('JSON' vs 'json'); splitting artifact when client sent comma-space vs comma separators expected as ', ' by the server; client and server subprotocol lists diverging after a version bump.","commonSituations":"GraphQL-over-WS (graphql-transport-ws expects 'graphql-transport-ws'), SOAP-over-WS, or app versioning via subprotocols; forgetting to pass the protocol in new WebSocket(url, ['v1.api']) on the client; renaming a protocol in one component only; header value casing differences between implementations.","solutions":["Pass the same protocol on both sides: client new WebSocket(url, 'chat.v2') and server Deno.upgradeWebSocket(req, { protocol: 'chat.v2' }).","Or let the server choose from the client's list: read req.headers.get('sec-websocket-protocol'), pick a supported one, and pass that as options.protocol.","If subprotocols are not needed, omit options.protocol entirely.","Match exactly - comparison is case-sensitive and against the raw header split on ', '."],"exampleFix":"// before\nconst { response } = Deno.upgradeWebSocket(req, { protocol: \"v2.api\" });\n// client: new WebSocket(url, [\"v1.api\"]) -> throws\n\n// after\nconst offered = (req.headers.get(\"sec-websocket-protocol\") ?? \"\")\n  .split(\",\").map((s) => s.trim());\nconst protocol = [\"v2.api\", \"v1.api\"].find((p) => offered.includes(p));\nconst { socket, response } = Deno.upgradeWebSocket(req, protocol ? { protocol } : {});","handlingStrategy":"validation","validationCode":"const offered = (req.headers.get(\"sec-websocket-protocol\") ?? \"\")\n  .split(\",\").map((s) => s.trim()).filter(Boolean);\nconst chosen = SUPPORTED_PROTOCOLS.find((p) => offered.includes(p));\nconst { socket, response } = Deno.upgradeWebSocket(req, chosen ? { protocol: chosen } : {});","typeGuard":"function pickSubprotocol(req: Request, supported: string[]): string | undefined { const offered = (req.headers.get(\"sec-websocket-protocol\") ?? \"\").split(\",\").map((s) => s.trim()); return supported.find((p) => offered.includes(p)); }","tryCatchPattern":"try { return Deno.upgradeWebSocket(req, { protocol: \"chat.v2\" }).response; } catch (e) { if (e instanceof TypeError && e.message.includes(\"protocol list\")) { return new Response(\"unsupported subprotocol\", { status: 400 }); } throw e; }","preventionTips":["Define the subprotocol list in one shared constant used by client and server.","Remember the match is case-sensitive and taken from the raw comma-split header.","If the client sent no protocols, don't request one server-side."],"tags":["websocket","subprotocol","negotiation","http-headers"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}