nodejs/node · error · TypeError

expected opts.excludeHeaderNames to be an array, got ${typeo

Error message

expected opts.excludeHeaderNames to be an array, got ${typeof excludeHeaderNames}

What it means

Thrown by the deduplicate interceptor when `excludeHeaderNames` is destructured from `opts` (default `[]`) but is not an Array. Unlike `skipHeaderNames` (which skips deduplication entirely when present), `excludeHeaderNames` excludes the named headers from the deduplication *key* so that requests differing only in those headers are still coalesced. The factory lowercases the entries into a Set at construction time.

Source

Thrown at deps/undici/src/lib/interceptor/deduplicate.js:41

    throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? 'null' : typeof opts}`)
  }

  if (!Array.isArray(methods)) {
    throw new TypeError(`expected opts.methods to be an array, got ${typeof methods}`)
  }

  for (const method of methods) {
    if (!util.safeHTTPMethods.includes(method)) {
      throw new TypeError(`expected opts.methods to only contain safe HTTP methods, got ${method}`)
    }
  }

  if (!Array.isArray(skipHeaderNames)) {
    throw new TypeError(`expected opts.skipHeaderNames to be an array, got ${typeof skipHeaderNames}`)
  }

  if (!Array.isArray(excludeHeaderNames)) {
    throw new TypeError(`expected opts.excludeHeaderNames to be an array, got ${typeof excludeHeaderNames}`)
  }

  if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {
    throw new TypeError(`expected opts.maxBufferSize to be a positive finite number, got ${maxBufferSize}`)
  }

  // Convert to lowercase Set for case-insensitive header matching
  const skipHeaderNamesSet = new Set(skipHeaderNames.map(name => name.toLowerCase()))

  // Convert to lowercase Set for case-insensitive header exclusion from deduplication key
  const excludeHeaderNamesSet = new Set(excludeHeaderNames.map(name => name.toLowerCase()))

  /**
   * Map of pending requests for deduplication
   * @type {Map<string, DeduplicationHandler>}
   */
  const pendingRequests = new Map()

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Pass an array of header-name strings: `excludeHeaderNames: ['x-request-id', 'trace-id']`.
  2. Confirm you actually want key-exclusion semantics vs. full skip — use `skipHeaderNames` if the header should bypass deduplication entirely.
  3. Normalize single-string config values into arrays before passing.

Example fix

// before
deduplicate({ excludeHeaderNames: 'x-request-id' })

// after
deduplicate({ excludeHeaderNames: ['x-request-id'] })
Defensive patterns

Strategy: validation

Validate before calling

function toStringArray(v) {
  if (v == null) return []
  if (Array.isArray(v)) return v.map(String)
  if (typeof v === 'string') return v.split(',').map(s => s.trim()).filter(Boolean)
  throw new TypeError('expected string or array of header names')
}

Type guard

function isHeaderNameArray(v) {
  return Array.isArray(v) && v.every(x => typeof x === 'string')
}

Prevention

When it happens

Trigger: Passing `excludeHeaderNames: 'x-request-id'` as a string; passing a Set or object instead of an Array; passing a delimited string. The `Array.isArray(excludeHeaderNames)` guard fails before the Set is built.

Common situations: Confusing `excludeHeaderNames` (header omitted from the key) with `skipHeaderNames` (header disables dedup); passing a single header as a bare string; reusing a config value typed as a string. Often appears together with errors 300-302 from one malformed options object.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/e37d29b0249a7f75. Report an issue: GitHub.