{"record":{"id":"28ded8150e580a75","repo":"denoland/deno","slug":"err-http2-status-invalid","errorCode":"ERR_HTTP2_STATUS_INVALID","errorMessage":"Invalid status code: ${statusCode}","messagePattern":"Invalid status code: (.+?)","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/http2.ts","lineNumber":2780,"sourceCode":"\n  if (!isDateSet && (options.sendDate == null || options.sendDate)) {\n    ArrayPrototypePush(headers, HTTP2_HEADER_DATE, utcDate());\n  }\n\n  validatePreparedResponseHeaders(headers, statusCode);\n\n  return { headers, statusCode };\n}\n\nfunction validatePreparedResponseHeaders(headers, statusCode) {\n  // This is intentionally stricter than the HTTP/1 implementation, which\n  // allows values between 100 and 999 (inclusive) in order to allow for\n  // backwards compatibility with non-spec compliant code. With HTTP/2,\n  // we have the opportunity to start fresh with stricter spec compliance.\n  // This will have an impact on the compatibility layer for anyone using\n  // non-standard, non-compliant status codes.\n  if (statusCode < 200 || statusCode > 599) {\n    throw new ERR_HTTP2_STATUS_INVALID(statusCode);\n  }\n\n  const neverIndex = headers[kSensitiveHeaders];\n  if (neverIndex !== undefined && !ArrayIsArray(neverIndex)) {\n    throw new ERR_INVALID_ARG_VALUE(\"headers[http2.neverIndex]\", neverIndex);\n  }\n}\n\nfunction tryClose(fd) {\n  fs.close(fd, (err) => {\n    if (err) throw err;\n  });\n}\n\nfunction processRespondWithFD(\n  self,\n  fd,\n  headers,","sourceCodeStart":2762,"sourceCodeEnd":2798,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/http2.ts#L2762-L2798","documentation":"validatePreparedResponseHeaders enforces that the :status on an HTTP/2 response is within 200-599, deliberately stricter than HTTP/1's 100-999, because HTTP/2 started fresh on spec compliance (the comment in the polyfill says exactly this). Statuses outside that range — including 1xx informational codes and anything >= 600 like custom 599s — throw ERR_HTTP2_STATUS_INVALID when passed to stream.respond() / respondWithFD().","triggerScenarios":"stream.respond({ ':status': 101 }) when proxying a WebSocket upgrade; :status 100/103 informational responses forwarded from an origin; custom non-standard codes like 599 or 999 copied from HTTP/1 proxy behavior.","commonSituations":"HTTP/1-to-HTTP/2 proxies forwarding 101 Switching Protocols (HTTP/2 forbids upgrade; extended CONNECT is the replacement); apps using 599 as an internal error sentinel; Early Hints (103) implementations that try to send them as a normal respond() on the same stream.","solutions":["Clamp or reject before responding: if (status < 200 || status > 599) map to a valid code (e.g. 501 or 500)","For upgrades, switch to RFC 8441 extended CONNECT (enableConnectProtocol) instead of forwarding 101","Handle 103 Early Hints via a separate informational mechanism, never through respond() on the same stream"],"exampleFix":"// before\nstream.respond({ ':status': 101 }); // throws ERR_HTTP2_STATUS_INVALID\n\n// after\nconst status = upstreamStatus >= 200 && upstreamStatus <= 599\n  ? upstreamStatus\n  : 501;\nstream.respond({ ':status': status });","handlingStrategy":"validation","validationCode":"const h2Status = (code: number): number =>\n  Number.isInteger(code) && code >= 200 && code <= 599 ? code : 501;\nstream.respond({ ':status': h2Status(upstreamStatus) });","typeGuard":"const isH2ValidStatus = (code: unknown): code is number =>\n  Number.isInteger(code) && (code as number) >= 200 && (code as number) <= 599;","tryCatchPattern":null,"preventionTips":["Validate upstream/proxied status codes against 200-599 before respond()","Do not forward 101 upgrades over http2; use extended CONNECT (RFC 8441)","Unit-test proxy paths with 1xx and >=600 codes from origins"],"tags":["http2","status-code","protocol-misuse","node-compat"],"backgroundTag":"http-status-code-invalid","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}