{"record":{"id":"59e7f445a560d57b","repo":"nodejs/node","slug":"expected-opts-methods-to-be-an-array-got-typeof","errorCode":null,"errorMessage":"expected opts.methods to be an array, got ${typeof methods}","messagePattern":"expected opts\\.methods to be an array, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"deps/undici/src/lib/interceptor/deduplicate.js","lineNumber":27,"sourceCode":"\n/**\n * @param {import('../../types/interceptors.d.ts').default.DeduplicateInterceptorOpts} [opts]\n * @returns {import('../../types/dispatcher.d.ts').default.DispatcherComposeInterceptor}\n */\nmodule.exports = (opts = {}) => {\n  const {\n    methods = ['GET'],\n    skipHeaderNames = [],\n    excludeHeaderNames = [],\n    maxBufferSize = 5 * 1024 * 1024\n  } = opts\n\n  if (typeof opts !== 'object' || opts === null) {\n    throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? 'null' : typeof opts}`)\n  }\n\n  if (!Array.isArray(methods)) {\n    throw new TypeError(`expected opts.methods to be an array, got ${typeof methods}`)\n  }\n\n  for (const method of methods) {\n    if (!util.safeHTTPMethods.includes(method)) {\n      throw new TypeError(`expected opts.methods to only contain safe HTTP methods, got ${method}`)\n    }\n  }\n\n  if (!Array.isArray(skipHeaderNames)) {\n    throw new TypeError(`expected opts.skipHeaderNames to be an array, got ${typeof skipHeaderNames}`)\n  }\n\n  if (!Array.isArray(excludeHeaderNames)) {\n    throw new TypeError(`expected opts.excludeHeaderNames to be an array, got ${typeof excludeHeaderNames}`)\n  }\n\n  if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {\n    throw new TypeError(`expected opts.maxBufferSize to be a positive finite number, got ${maxBufferSize}`)","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/deps/undici/src/lib/interceptor/deduplicate.js#L9-L45","documentation":"Thrown by the deduplicate interceptor's factory function when the `methods` option is destructured from `opts` but is not an Array. The deduplicate interceptor merges concurrent identical safe-method requests into a single network call, so it must know which HTTP methods are eligible. Because the factory destructures `methods` with a default of `['GET']`, this fires only when you explicitly pass a non-array value. It is a synchronous TypeError raised at interceptor construction time (when you call `createDeduplicateInterceptor(opts)` or compose it), not per-request.","triggerScenarios":"Calling the deduplicate interceptor factory with `opts.methods` set to a string (e.g. `'GET'`), a number, a plain object, `true`, or any other non-array. Passing `methods: 'GET'` instead of `methods: ['GET']` is the canonical trigger. The check `Array.isArray(methods)` fails before any method-content validation runs.","commonSituations":"Copying a single method from a config file as a bare string; migrating from a single-method API to the array form; reading `methods` out of JSON where it was serialized as a scalar; TypeScript users bypassing types with `as any`. The default `['GET']` masks the requirement, so developers first encounter it only when they try to add `HEAD`.","solutions":["Change the `methods` option to an array of HTTP method strings, e.g. `methods: ['GET']` or `methods: ['GET', 'HEAD']`.","If loading options from external config, coerce with `Array.isArray(opts.methods) ? opts.methods : [opts.methods]` before passing them in.","If you meant to deduplicate only one method, pass a one-element array rather than a bare string.","Audit the surrounding config object for other shape mismatches (skipHeaderNames, excludeHeaderNames, maxBufferSize) since they validate in the same block."],"exampleFix":"// before\nconst agent = new Agent().compose([deduplicate({ methods: 'GET' })])\n\n// after\nconst agent = new Agent().compose([deduplicate({ methods: ['GET'] })])","handlingStrategy":"validation","validationCode":"function validateDeduplicateOpts(opts) {\n  if (!Array.isArray(opts.methods)) {\n    opts.methods = Array.isArray(opts.methods) ? opts.methods : [opts.methods].filter(Boolean)\n  }\n  if (!Array.isArray(opts.methods)) {\n    throw new TypeError('opts.methods must be an array of safe HTTP methods')\n  }\n  return opts\n}","typeGuard":"function isMethodArray(v) {\n  return Array.isArray(v) && v.every(m => typeof m === 'string')\n}","tryCatchPattern":null,"preventionTips":["Always pass `methods` as an array even for a single method.","Load interceptor config through a shared validator in your bootstrap code.","Enable TypeScript strict types from undici's interceptor definitions."],"tags":["undici","interceptor","deduplicate","validation","http-method","config"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}