denoland/deno · error · TypeError

Dispatcher connect.ca must be a string, Buffer, or ArrayBuff

Error message

Dispatcher connect.ca must be a string, Buffer, or ArrayBuffer

What it means

In Deno's bundled undici, dispatcher/Agent options.connect.ca is normalized by normalizeCaCerts: each entry must be a PEM string, an ArrayBuffer view (Buffer/Uint8Array), or an ArrayBuffer; a non-array value is wrapped into a list automatically. Any other type (X509Certificate, KeyObject, number, plain object, URL) throws this TypeError - note it has no code property, unlike the node: errors.

Source

Thrown at ext/node/polyfills/internal/deps/undici/undici.js:39

  "Deno.internal.node.undici.dispatcherOptions",
);
const kGlobalDispatcher = SymbolFor(
  "Deno.internal.node.undici.globalDispatcher",
);

function normalizeCaCerts(ca) {
  const certs = ArrayIsArray(ca) ? ca : [ca];
  return ArrayPrototypeMap(certs, (cert) => {
    if (typeof cert === "string") {
      return cert;
    }
    if (isArrayBufferView(cert)) {
      return new TextDecoder().decode(cert);
    }
    if (isAnyArrayBuffer(cert)) {
      return new TextDecoder().decode(new Uint8Array(cert));
    }
    throw new TypeError(
      "Dispatcher connect.ca must be a string, Buffer, or ArrayBuffer",
    );
  });
}

class Agent {
  constructor(options = { __proto__: null }) {
    const connect = options.connect ?? { __proto__: null };
    const dispatcherOptions = { __proto__: null };

    if (connect.rejectUnauthorized === false) {
      dispatcherOptions.unsafelyIgnoreCertificateErrors = true;
    }

    if (connect.ca !== undefined) {
      dispatcherOptions.caCerts = normalizeCaCerts(connect.ca);
    }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass the PEM string or Buffer directly: connect: { ca: pem } (single values are fine - non-arrays are wrapped).
  2. If you hold an X509Certificate, unwrap it: connect: { ca: cert.toString() } (toString returns the PEM).
  3. Sanitize config before constructing the Agent: keep only string/Buffer entries and drop the rest.

Example fix

// before
const agent = new Agent({ connect: { ca: new X509Certificate(pem) } });

// after
const agent = new Agent({ connect: { ca: pem } }); // PEM string or Buffer
// or, from an X509Certificate: connect: { ca: cert.toString() }
Defensive patterns

Strategy: validation

Validate before calling

function normalizeCa(ca) {
  const list = Array.isArray(ca) ? ca : [ca];
  return list.filter((c) =>
    typeof c === 'string' || ArrayBuffer.isView(c) || c instanceof ArrayBuffer
  );
}
const agent = new Agent({ connect: { ca: normalizeCa(config.ca) } });

Type guard

const isCaEntry = (v) =>
  typeof v === 'string' || ArrayBuffer.isView(v) || v instanceof ArrayBuffer;

Try / catch

try {
  new Agent({ connect: { ca } });
} catch (err) {
  if (err instanceof TypeError && /connect\.ca/.test(err.message)) {
    agent = new Agent({ connect: { ca: pemString } }); // fall back to raw PEM
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: new Agent({ connect: { ca: new X509Certificate(pem) } }); ca: 123 from bad config; an array containing null or objects parsed from JSON; passing a Node https.Agent-style options object wholesale as connect options.

Common situations: Corporate MITM proxy setups injecting custom CAs; migrating Node fetch/undici configuration where ca shapes differ; config-driven HTTP clients where the CA field is optional and sometimes an object.

Related errors


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