denoland/deno · error · NodeTypeError

ERR_UNESCAPED_CHARACTERS

ERR_UNESCAPED_CHARACTERS

Error message

Request path contains unescaped characters

What it means

ClientRequest validates options.path against INVALID_PATH_REGEX = /[^\u0021-\u00ff]/, i.e. any character below '!' (\u0021) or above \u00ff is rejected. This catches spaces (\u0020), control characters, and non-Latin-1 characters (CJK, emoji) that must be percent-encoded in a request target. ERR_UNESCAPED_CHARACTERS is thrown before the request is ever sent.

Source

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

  } else if (typeof agent.addRequest !== "function") {
    throw new ERR_INVALID_ARG_TYPE(
      "options.agent",
      ["Agent-like Object", "undefined", "false"],
      agent,
    );
  }
  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;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Encode dynamic segments: '/search?q=' + encodeURIComponent(q)
  2. Build the whole target with URLSearchParams and use url.pathname + url.search
  3. For full-path rewrites, run encodeURI() once and verify no raw spaces/control chars remain

Example fix

// before
const req = http.request({ host: 'x.test', path: `/search?q=${term}` }); // term = 'hello world'

// after
const qs = new URLSearchParams({ q: term }).toString();
const req = http.request({ host: 'x.test', path: `/search?${qs}` });
Defensive patterns

Strategy: validation

Validate before calling

const INVALID_PATH = /[^\u0021-\u00ff]/;
const path = buildPath(params);
if (INVALID_PATH.test(path)) throw new Error('path needs encoding');
http.request({ host, path: encodeURI(path) });

Type guard

function isEscapedPath(p) { return typeof p === 'string' && !/[^\u0021-\u00ff]/.test(p); }

Try / catch

try { http.request({ host, path }); } catch (e) { if (e.code === 'ERR_UNESCAPED_CHARACTERS') { /* re-encode and retry once with encodeURI(path) */ } else throw e; }

Prevention

When it happens

Trigger: http.request({ path: '/search?q=hello world' }) (raw space); path containing raw UTF-8 like '/tags/日本語'; path built from unencoded user input with newlines or tabs.

Common situations: Building query strings by string concatenation instead of URLSearchParams; localizing routes or passing user-typed search text straight into path; proxying requests whose original path was never normalized.

Related errors


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