denoland/deno · error · RangeError
ERR_HTTP2_INVALID_INFO_STATUS
ERR_HTTP2_INVALID_INFO_STATUS
Error message
Invalid informational status code: ${headers[HTTP2_HEADER_STATUS]} What it means
additionalHeaders() may only carry informational status codes: 100-199 excluding 101 (which has its own error). Any :status below 100, at or above 200, or a non-coercible value (bitwise |= 0 turns '103abc' into garbage) throws ERR_HTTP2_INVALID_INFO_STATUS.
Source
Thrown at ext/node/polyfills/http2.ts:3463
if (this.destroyed || this.closed) {
throw new ERR_HTTP2_INVALID_STREAM();
}
if (this.headersSent) {
throw new ERR_HTTP2_HEADERS_AFTER_RESPOND();
}
assertIsObject(headers, "headers");
headers = ObjectAssign({ __proto__: null }, headers);
debugStreamObj(this, "sending additional headers");
if (headers[HTTP2_HEADER_STATUS] != null) {
const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
if (statusCode === HTTP_STATUS_SWITCHING_PROTOCOLS) {
throw new ERR_HTTP2_STATUS_101();
}
if (statusCode < 100 || statusCode >= 200) {
throw new ERR_HTTP2_INVALID_INFO_STATUS(headers[HTTP2_HEADER_STATUS]);
}
}
this[kUpdateTimer]();
const headersList = buildNgHeaderString(
headers,
assertValidPseudoHeaderResponse,
this.session[kStrictSingleValueFields],
);
if (!this[kInfoHeaders]) {
this[kInfoHeaders] = [headers];
} else {
ArrayPrototypePush(this[kInfoHeaders], headers);
}
const ret = this[kHandle].info(headersList[0], headersList[1]);
if (ret < 0) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Only ever send 100-199 (typically 103) via additionalHeaders(); send final statuses with respond()
- Build the hints object explicitly with __proto__: null and only 1xx :status + link headers
- Validate statusCode >= 100 && statusCode < 200 && statusCode !== 101 before the call
- Keep hint headers and final headers in separate constants so they cannot be swapped
Example fix
// before
stream.additionalHeaders(responseHeaders); // contains ":status": 200
// after
const hints = Object.assign(Object.create(null), {
":status": 103,
link: "</style.css>; rel=preload",
});
stream.additionalHeaders(hints); Defensive patterns
Strategy: validation
Validate before calling
const status = Number(headers[":status"]);
if (!(Number.isInteger(status) && status >= 100 && status < 200 && status !== 101)) {
return; // not a valid informational code; skip hints
}
stream.additionalHeaders(headers); Type guard
function isH2SafeInfoStatus(status) {
const n = Number(status);
return Number.isInteger(n) && n >= 100 && n < 200 && n !== 101;
} Try / catch
try {
stream.additionalHeaders(headers);
} catch (err) {
if (err.code === "ERR_HTTP2_INVALID_INFO_STATUS") return; // bad hint value; skip
throw err;
} Prevention
- Build hint objects separately from final response headers; never reuse the respond() headers
- Hard-code ':status': 103 for Early Hints instead of forwarding variable statuses
When it happens
Trigger: Passing ':status': 200/204 to additionalHeaders(); forwarding a final upstream status into the hints path; a string status like '103' only working by accident while others coerce to out-of-range numbers.
Common situations: Hint middleware reusing the real response headers object; copy-paste of respond() headers into additionalHeaders(); logging/status plumbing that assumes any status is valid anywhere.
Related errors
- ERR_INVALID_ARG_TYPE
- ERR_HTTP2_HEADERS_AFTER_RESPOND
- ERR_HTTP2_STATUS_101
- ERR_MISSING_ARGS
- No callback function supplied
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/7c22537f78456a20.
Report an issue: GitHub.