denoland/deno · error · NodeTypeError

ERR_INVALID_PROTOCOL

ERR_INVALID_PROTOCOL

Error message

Protocol "${protocol}" not supported. Expected "${expectedProtocol}"

What it means

ClientRequest derives expectedProtocol from the agent (this.agent?.protocol, falling back to the default agent's) and compares it with the effective protocol from options.protocol or the default. A mismatch — most commonly https: with the http module's globalAgent — throws ERR_INVALID_PROTOCOL. The check runs after path validation, before any connection is made.

Source

Thrown at ext/node/polyfills/_http_client.js:513

    );
  }
  this.agent = agent;

  const protocol = options.protocol || defaultAgent.protocol;
  let expectedProtocol = defaultAgent.protocol;
  if (this.agent?.protocol) {
    expectedProtocol = this.agent.protocol;
  }

  if (options.path) {
    const path = String(options.path);
    if (INVALID_PATH_REGEX.test(path)) {
      throw new ERR_UNESCAPED_CHARACTERS("Request path");
    }
  }

  if (protocol !== expectedProtocol) {
    throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
  }

  const defaultPort = options.defaultPort ||
    (this.agent?.defaultPort);

  const optsWithoutSignal = { __proto__: null, ...options };

  // The `_proxy*` fields are internal transport details set only by the proxy
  // selection below. A caller must not be able to supply them directly: doing
  // so would route the request through an arbitrary proxy while bypassing the
  // target permission check that the proxy branch performs. Strip any that came
  // in via `options` so only the values computed here are honored.
  delete optsWithoutSignal._proxy;
  delete optsWithoutSignal._proxyTargetHost;
  delete optsWithoutSignal._proxyTargetPort;
  delete optsWithoutSignal._proxyProtocol;
  delete optsWithoutSignal._proxyUseProxyConnection;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Use the matching module: https.request for https: targets, http.request for http:
  2. Construct the agent from the same module as the request: new https.Agent({ keepAlive: true })
  3. Omit the custom agent (or options.protocol) and let the default agent for the module you called supply the protocol

Example fix

// before
const agent = new http.Agent({ keepAlive: true });
const req = https.request('https://api.test/x', { agent });

// after
const agent = new https.Agent({ keepAlive: true });
const req = https.request('https://api.test/x', { agent });
Defensive patterns

Strategy: validation

Validate before calling

const target = new URL(urlStr);
const mod = target.protocol === 'https:' ? https : http;
if (agent && agent.protocol && agent.protocol !== target.protocol) {
  throw new Error(`agent protocol ${agent.protocol} != ${target.protocol}`);
}
mod.request(target, { agent });

Type guard

const protocolsMatch = (reqProtocol, agent) => !agent?.protocol || agent.protocol === reqProtocol;

Try / catch

try { https.request(url, { agent }); } catch (e) { if (e.code === 'ERR_INVALID_PROTOCOL') { /* rebuild agent from the https module */ } else throw e; }

Prevention

When it happens

Trigger: https.get('https://svc', { agent: new http.Agent() }) or http.request('https://...') where the default agent is http's; explicitly setting options.protocol: 'https:' while using an agent constructed for http; wiring a shared agent across both modules.

Common situations: Importing http instead of https (or a shared keepAlive agent used for both); refactoring a client from http to https without updating the agent; test doubles that inject a fake agent with the wrong protocol string.

Related errors


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