denoland/deno · error · RangeError
ERR_HTTP2_STATUS_INVALID
ERR_HTTP2_STATUS_INVALID
Error message
Invalid status code: ${statusCode} What it means
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().
Source
Thrown at ext/node/polyfills/http2.ts:2780
if (!isDateSet && (options.sendDate == null || options.sendDate)) {
ArrayPrototypePush(headers, HTTP2_HEADER_DATE, utcDate());
}
validatePreparedResponseHeaders(headers, statusCode);
return { headers, statusCode };
}
function validatePreparedResponseHeaders(headers, statusCode) {
// This is intentionally stricter than the HTTP/1 implementation, which
// allows values between 100 and 999 (inclusive) in order to allow for
// backwards compatibility with non-spec compliant code. With HTTP/2,
// we have the opportunity to start fresh with stricter spec compliance.
// This will have an impact on the compatibility layer for anyone using
// non-standard, non-compliant status codes.
if (statusCode < 200 || statusCode > 599) {
throw new ERR_HTTP2_STATUS_INVALID(statusCode);
}
const neverIndex = headers[kSensitiveHeaders];
if (neverIndex !== undefined && !ArrayIsArray(neverIndex)) {
throw new ERR_INVALID_ARG_VALUE("headers[http2.neverIndex]", neverIndex);
}
}
function tryClose(fd) {
fs.close(fd, (err) => {
if (err) throw err;
});
}
function processRespondWithFD(
self,
fd,
headers,View on GitHub (pinned to 9ad36f7a2c)
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
Example fix
// before
stream.respond({ ':status': 101 }); // throws ERR_HTTP2_STATUS_INVALID
// after
const status = upstreamStatus >= 200 && upstreamStatus <= 599
? upstreamStatus
: 501;
stream.respond({ ':status': status }); Defensive patterns
Strategy: validation
Validate before calling
const h2Status = (code: number): number =>
Number.isInteger(code) && code >= 200 && code <= 599 ? code : 501;
stream.respond({ ':status': h2Status(upstreamStatus) }); Type guard
const isH2ValidStatus = (code: unknown): code is number => Number.isInteger(code) && (code as number) >= 200 && (code as number) <= 599;
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ERR_HTTP2_NO_SOCKET_MANIPULATION
- ERR_HTTP2_STATUS_101
- ERR_HTTP2_INVALID_INFO_STATUS
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_URL
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/28ded8150e580a75.
Report an issue: GitHub.