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
- Encode dynamic segments: '/search?q=' + encodeURIComponent(q)
- Build the whole target with URLSearchParams and use url.pathname + url.search
- 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
- Build query strings with URLSearchParams, never concatenation
- encodeURIComponent every dynamic path segment
- Centralize request-target construction in one helper
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
- ERR_INVALID_ARG_TYPE
- ERR_INVALID_URL
- ERR_INVALID_HTTP_TOKEN
- ERR_HTTP_HEADERS_SENT
- ERR_INVALID_HTTP_TOKEN
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/371c66e7c4a7f1ad.
Report an issue: GitHub.