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

  1. Only ever send 100-199 (typically 103) via additionalHeaders(); send final statuses with respond()
  2. Build the hints object explicitly with __proto__: null and only 1xx :status + link headers
  3. Validate statusCode >= 100 && statusCode < 200 && statusCode !== 101 before the call
  4. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/7c22537f78456a20. Report an issue: GitHub.