denoland/deno · error · NodeTypeError

ERR_INVALID_ARG_VALUE

ERR_INVALID_ARG_VALUE

Error message

The argument 'headers' is invalid. Received ${inspect(obj)}

What it means

In writeHead's slow path - taken when setHeader/appendHeader were already used (this[kOutHeaders] is set) - an array argument must be a flat [name1, value1, name2, value2, ...] list. An odd-length array leaves a name without a value, so the polyfill throws ERR_INVALID_ARG_VALUE('headers', obj) exactly like Node.

Source

Thrown at ext/node/polyfills/_http_server.js:534

  if (typeof reason === "string") {
    this.statusMessage = reason;
  } else {
    this.statusMessage ||= STATUS_CODES[statusCode] || "unknown";
    obj ??= reason;
  }
  this.statusCode = statusCode;

  // Enforce no body for 204 and 304 responses
  if (statusCode === 204 || statusCode === 304) {
    this._hasBody = false;
  }

  let headers;
  if (this[kOutHeaders]) {
    // Slow-case: progressive API and header fields are passed.
    if (ArrayIsArray(obj)) {
      if (obj.length % 2 !== 0) {
        throw new ERR_INVALID_ARG_VALUE("headers", obj);
      }
      for (let n = 0; n < obj.length; n += 2) {
        const k = obj[n + 0];
        if (k) this.removeHeader(k);
      }
      for (let n = 0; n < obj.length; n += 2) {
        const k = obj[n + 0];
        if (k) this.appendHeader(k, obj[n + 1]);
      }
    } else if (obj) {
      const keys = ObjectKeys(obj);
      for (let i = 0; i < keys.length; i++) {
        const k = keys[i];
        if (k) this.setHeader(k, obj[k]);
      }
    }
    headers = this[kOutHeaders];
  } else {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Fix the array to even length with strict name/value pairs
  2. Prefer the object form writeHead(200, { 'content-type': 'text/html' }) which cannot be odd
  3. When the array is dynamic, assert headers.length % 2 === 0 before calling
  4. Centralize header construction in one tested helper

Example fix

// before
const h = ['content-type', 'text/html'];
if (cors) h.push('access-control-allow-origin'); // odd length -> throws
res.writeHead(200, h);

// after
const h = { 'content-type': 'text/html' };
if (cors) h['access-control-allow-origin'] = '*';
res.writeHead(200, h);
Defensive patterns

Strategy: validation

Validate before calling

function validHeaderPairs(headers) {
  if (!Array.isArray(headers)) return true;
  return headers.length % 2 === 0;
}
if (validHeaderPairs(pairs)) {
  res.writeHead(200, reason, pairs);
}

Type guard

function isHeaderPairArray(v) {
  return !Array.isArray(v) || v.length % 2 === 0;
}

Try / catch

try {
  res.writeHead(200, reason, pairs);
} catch (e) {
  if (e.code === 'ERR_INVALID_ARG_VALUE') {
    res.writeHead(200, reason); // fall back to headers set via setHeader
  } else throw e;
}

Prevention

When it happens

Trigger: Mixing the progressive API (res.setHeader earlier) with writeHead(200, reason, ['Content-Type', 'text/html', 'X-Extra']) whose array has odd length; dynamically built pairs arrays where a key is pushed without its value.

Common situations: Conditionally appending header pairs with array.push(name) while forgetting the value; spreading a computed array that can drop entries; header builders whose schema drifted so a value can be undefined and skipped.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/82a6f730cc261cda. Report an issue: GitHub.