nodejs/node · error · InvalidArgumentError

throwOnMaxRedirect must be a boolean

Error message

throwOnMaxRedirect must be a boolean

What it means

Thrown from the RedirectHandler constructor when opts.throwOnMaxRedirect is present but is not a boolean. throwOnMaxRedirect is a per-request flag that, when true, makes the handler throw instead of silently stopping once maxRedirections is exhausted. It must be a real boolean (or null/undefined to leave it unset); truthy/falsy values like 'true', 1, 0, or 'yes' are rejected because typeof is checked.

Source

Thrown at deps/undici/src/lib/handler/redirect-handler.js:27

const noop = () => {}

class RedirectHandler {
  static buildDispatch (dispatcher, maxRedirections) {
    if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
      throw new InvalidArgumentError('maxRedirections must be a positive number')
    }

    const dispatch = dispatcher.dispatch.bind(dispatcher)
    return (opts, originalHandler) => dispatch(opts, new RedirectHandler(dispatch, maxRedirections, opts, originalHandler))
  }

  constructor (dispatch, maxRedirections, opts, handler) {
    if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) {
      throw new InvalidArgumentError('maxRedirections must be a positive number')
    }

    if (opts.throwOnMaxRedirect != null && typeof opts.throwOnMaxRedirect !== 'boolean') {
      throw new InvalidArgumentError('throwOnMaxRedirect must be a boolean')
    }

    this.dispatch = dispatch
    this.location = null
    const { maxRedirections: _, stripHeadersOnRedirect, stripHeadersOnCrossOriginRedirect, ...cleanOpts } = opts
    this.opts = cleanOpts // opts must be a copy, exclude maxRedirections
    this.opts.body = util.wrapRequestBody(this.opts.body)
    this.stripHeadersOnRedirect = normalizeStripHeaders(stripHeadersOnRedirect, 'stripHeadersOnRedirect')
    this.stripHeadersOnCrossOriginRedirect = normalizeStripHeaders(stripHeadersOnCrossOriginRedirect, 'stripHeadersOnCrossOriginRedirect')
    this.maxRedirections = maxRedirections
    this.handler = handler
    this.history = []
  }

  onRequestStart (controller, context) {
    this.handler.onRequestStart?.(controller, { ...context, history: this.history })
  }

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Coerce to a strict boolean before passing: throwOnMaxRedirect: Boolean(value) (or only set it when value is already boolean).
  2. Omit the option entirely if you do not need throw-on-max behavior; it defaults to unset.
  3. Validate incoming config with a boolean schema field (e.g. zod/ajv) at the trust boundary.

Example fix

// before
await client.request({ path, method: 'GET', throwOnMaxRedirect: req.query.strict })

// after
const strict = req.query.strict === 'true' || req.query.strict === true
await client.request({ path, method: 'GET', throwOnMaxRedirect: strict })
Defensive patterns

Strategy: type-guard

Validate before calling

function strictBool(v) { return typeof v === 'boolean' ? v : undefined }

Type guard

function isBool(v) { return v == null || typeof v === 'boolean' }

Try / catch

try { await client.request({ throwOnMaxRedirect: flag }) } catch (e) { if (e.code === 'UND_ERR_INVALID_ARG') { await client.request({ /* drop throwOnMaxRedirect */ }) } else throw e }

Prevention

When it happens

Trigger: Passing throwOnMaxRedirect as a string ('true'/'false'), a number (1/0), or any non-boolean truthy/falsy value in per-request dispatch opts or in the request options of an Agent configured with redirects.

Common situations: Flags parsed from query strings, headers, or JSON config arriving as strings; code that assumes 'throw on truthy' semantics; copying a value from a loosely-typed settings object.

Related errors


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