denoland/deno · error · NodeTypeError
ERR_HTTP2_INVALID_PSEUDOHEADER
ERR_HTTP2_INVALID_PSEUDOHEADER
Error message
"${key}" is an invalid pseudoheader or is used incorrectly What it means
HTTP/2 pseudo-headers (keys starting with ':') are fixed by RFC 7540; only :status, :method, :authority, :scheme, :path and :protocol are legal (kValidPseudoHeaders, util.ts:120). When request headers are prepared, every ':'-prefixed key is checked against that set and any other pseudo-header throws ERR_HTTP2_INVALID_PSEUDOHEADER.
Source
Thrown at ext/node/polyfills/internal/http2/util.ts:613
function isIllegalConnectionSpecificHeader(name, value) {
switch (name) {
case HTTP2_HEADER_CONNECTION:
case HTTP2_HEADER_UPGRADE:
case HTTP2_HEADER_HTTP2_SETTINGS:
case HTTP2_HEADER_KEEP_ALIVE:
case HTTP2_HEADER_PROXY_CONNECTION:
case HTTP2_HEADER_TRANSFER_ENCODING:
return true;
case HTTP2_HEADER_TE:
return value !== "trailers";
default:
return false;
}
}
const assertValidPseudoHeader = hideStackFrames((key) => {
if (!kValidPseudoHeaders.has(key)) {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
}
});
const assertValidPseudoHeaderResponse = hideStackFrames((key) => {
if (key !== ":status") {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
}
});
const assertValidPseudoHeaderTrailer = hideStackFrames((key) => {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
});
/**
* Takes a request headers array, validates it and sets defaults, and returns
* the resulting headers in NgHeaders string list format.
* @returns {object}
*/View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Inspect the thrown message for the offending key and rename it to a normal header without the leading ':' (unless it is one of the six standard pseudo-headers).
- If you are proxying, strip or map unknown ':'-prefixed keys before calling request().
- Use the constants http2.constants.HTTP2_HEADER_METHOD / :PATH / :AUTHORITY / :SCHEME / :PROTOCOL / :STATUS instead of hand-typing pseudo-header names.
Example fix
// before const headers = [':method', 'GET', ':ver', '2']; // ':ver' is not a pseudo-header // after const headers = [':method', 'GET', 'x-ver', '2'];
Defensive patterns
Strategy: validation
Validate before calling
const VALID = new Set([':status', ':method', ':authority', ':scheme', ':path', ':protocol']);
const safe = headers.filter((k, i) => i % 2 === 1 || !String(k).startsWith(':') || VALID.has(k)); Type guard
const isPseudoHeader = (k: string) => k.startsWith(':');
const isValidPseudo = (k: string) =>
[':status', ':method', ':authority', ':scheme', ':path', ':protocol'].includes(k); Try / catch
try { client.request(h); } catch (e) { if (e.code === 'ERR_HTTP2_INVALID_PSEUDOHEADER') { /* message contains the bad key; rename or strip it */ } throw e; } Prevention
- Use http2.constants.HTTP2_HEADER_* instead of hand-written ':'-keys
- Filter ':'-prefixed keys from foreign/proxied header maps before request()
When it happens
Trigger: Passing a request headers array containing a ':'-prefixed key outside the valid set, e.g. [':method','GET', ':custom','x'] to http2session.request() or client.request(); assertValidPseudoHeader (util.ts:611) rejects ':custom'.
Common situations: Forwarding arbitrary header maps from proxies/gateways that already mangled ':'-prefixed names, building headers dynamically from user input where a stray ':' prefixes a normal header, or copying gRPC-style metadata (':path' ok, ':grpc-custom' not) into plain HTTP/2 requests.
Related errors
- ERR_INVALID_ARG_VALUE
- ERR_HTTP2_CONNECT_AUTHORITY
- ERR_HTTP2_CONNECT_SCHEME
- ERR_HTTP2_CONNECT_PATH
- ERR_HTTP2_HEADER_SINGLE_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/872204b8bf6ba3a5.
Report an issue: GitHub.