denoland/deno · error · NodeTypeError

ERR_HTTP2_INVALID_HEADER_VALUE

ERR_HTTP2_INVALID_HEADER_VALUE

Error message

Invalid value "${value}" for header "${name}"

What it means

assertValidHeader in http2 compat also checks the value: a header value of undefined or null throws ERR_HTTP2_INVALID_HEADER_VALUE, because HTTP/2 cannot encode a header with no value. Every header set through setHeader/appendHeader/setTrailer/writeHead must carry an actual value (string, number, or array).

Source

Thrown at ext/node/polyfills/internal/http2/compat.js:105

let statusConnectionHeaderWarned = false;

// Defines and implements an API compatibility layer on top of the core
// HTTP/2 implementation, intended to provide an interface that is as
// close as possible to the current require('http') API

const assertValidHeader = hideStackFrames((name, value) => {
  if (
    name === "" ||
    typeof name !== "string" ||
    StringPrototypeIncludes(name, " ")
  ) {
    throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError("Header name", name);
  }
  if (isPseudoHeader(name)) {
    throw new ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED.HideStackFramesError();
  }
  if (value === undefined || value === null) {
    throw new ERR_HTTP2_INVALID_HEADER_VALUE.HideStackFramesError(value, name);
  }
  if (!isConnectionHeaderAllowed(name, value)) {
    connectionHeaderMessageWarn();
  }
});

function isPseudoHeader(name) {
  switch (name) {
    case HTTP2_HEADER_STATUS: // :status
    case HTTP2_HEADER_METHOD: // :method
    case HTTP2_HEADER_PATH: // :path
    case HTTP2_HEADER_AUTHORITY: // :authority
    case HTTP2_HEADER_SCHEME: // :scheme
      return true;
    default:
      return false;
  }
}

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Skip the header entirely when the value is null or undefined
  2. Default it when an empty value is meaningful: setHeader(name, value ?? "")
  3. Filter header maps before writeHead: Object.fromEntries(Object.entries(h).filter(([, v]) => v != null))

Example fix

// before
for (const [k, v] of Object.entries(config.headers)) res.setHeader(k, v); // undefined v throws

// after
for (const [k, v] of Object.entries(config.headers)) {
  if (v !== undefined && v !== null) res.setHeader(k, v);
}
Defensive patterns

Strategy: validation

Validate before calling

function hasHeaderValue(v) {
  return v !== undefined && v !== null;
}
for (const [k, v] of Object.entries(headers)) {
  if (hasHeaderValue(v)) res.setHeader(k, v);
}

Type guard

function isDefiniteHeaderValue(
  v: unknown,
): v is string | number | string[] | number[] {
  return v !== undefined && v !== null;
}

Try / catch

try {
  res.setHeader(k, v);
} catch (err) {
  if (err.code === "ERR_HTTP2_INVALID_HEADER_VALUE" && v == null) return; // just skip
  throw err;
}

Prevention

When it happens

Trigger: res.setHeader("x-optional", undefined) from optional config or destructuring; object-spread headers containing undefined fields passed to writeHead(); trailer values fetched from a Map/lookup that returned null or undefined.

Common situations: Optional feature flags: setHeader("x-deprecated", flags.deprecated) where the field is absent; spreads like { ...commonHeaders, ...overrides } where some keys are undefined; JSON config with null values fed straight into headers.

Related errors


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