denoland/deno · error · NodeTypeError

ERR_HTTP2_HEADER_SINGLE_VALUE

ERR_HTTP2_HEADER_SINGLE_VALUE

Error message

Header field "${key}" must only have a single value

What it means

Some HTTP/2 header fields are single-valued by spec (kSingleValueHeaders, util.ts:131 — the pseudo-headers plus content-length, authorization, age, content-encoding/language/location/length, etc.). If such a key arrives with an ARRAY value of length > 1, buildNgHeaderString throws ERR_HTTP2_HEADER_SINGLE_VALUE (util.ts:843) — the value cannot be joined or repeated.

Source

Thrown at ext/node/polyfills/internal/http2/util.ts:843

    (v) => StringPrototypeToLowerCase(v),
  );

  function processHeader(key, value) {
    key = StringPrototypeToLowerCase(key);
    const isSingleValueHeader = strictSingleValueFields &&
      kSingleValueHeaders.has(key);
    let isArray = ArrayIsArray(value);
    if (isArray) {
      switch (value.length) {
        case 0:
          return;
        case 1:
          value = String(value[0]);
          isArray = false;
          break;
        default:
          if (isSingleValueHeader) {
            throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
          }
      }
    } else {
      value = String(value);
    }
    if (isSingleValueHeader) {
      if (singles.has(key)) {
        throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
      }
      singles.add(key);
    }
    const flags = ArrayPrototypeIncludes(neverIndex, key)
      ? kNeverIndexFlag
      : kNoHeaderFlags;
    if (key[0] === ":") {
      const err = assertValuePseudoHeader(key);
      if (err !== undefined) {
        throw err;

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Ensure single-value headers always end up as one scalar string before calling request()/respond(): collapse arrays of length 1 and reject/resolve longer ones.
  2. If two values exist, decide which one is authoritative (e.g. take the first, or fail with your own error) instead of passing both.
  3. Keep a deny-list check for kSingleValueHeaders-style fields when merging header objects.

Example fix

// before
stream.respond({ ':status': 200, 'content-length': ['10', '20'] });

// after
stream.respond({ ':status': 200, 'content-length': '10' });
Defensive patterns

Strategy: validation

Validate before calling

const SINGLE = new Set([':status', ':method', ':authority', ':scheme', ':path', ':protocol', 'content-length', 'authorization', 'age', 'content-encoding', 'content-language', 'content-location']);
for (const [k, v] of Object.entries(headers)) {
  if (SINGLE.has(k.toLowerCase()) && Array.isArray(v)) {
    if (v.length > 1) throw new Error(`${k} must be single-valued`);
    headers[k] = v[0];
  }
}

Type guard

const isSingleValueHeader = (k: string) => SINGLE.has(k.toLowerCase());

Prevention

When it happens

Trigger: Passing headers like { 'content-length': ['42','43'] } or an array pair ['authorization', ['a','b']] to http2session.request()/stream.respond(); arrays of length 0 are skipped and length 1 is unwrapped, only length >= 2 with a single-value key throws here.

Common situations: Merging header sets where one side already had an array (set-cookie is multi-valued and fine, but people over-generalize); query-string parsers producing arrays for repeated params reused as headers; middleware that normalizes every header value into [value].

Related errors


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