denoland/deno · error · NodeError
ERR_HTTP2_NO_SOCKET_MANIPULATION
ERR_HTTP2_NO_SOCKET_MANIPULATION
Error message
HTTP/2 sockets should not be directly manipulated (e.g. read and written)
What it means
In http2 compat, http2Stream.socket is a Proxy that whitelists safe socket members. Reading socket.write, socket.read, socket.pause, or socket.resume throws ERR_HTTP2_NO_SOCKET_MANIPULATION: an h2 stream is multiplexed over the session's single socket, so writing or pausing it directly would corrupt other streams on the same connection. setTimeout and plain property passthrough are allowed.
Source
Thrown at ext/node/polyfills/internal/http2/compat.js:253
case "readable": {
if (stream.destroyed) {
return false;
}
const request = stream[kRequest];
return request ? request.readable : stream.readable;
}
case "setTimeout": {
const session = stream.session;
if (session !== undefined) {
return FunctionPrototypeBind(session.setTimeout, session);
}
return FunctionPrototypeBind(stream.setTimeout, stream);
}
case "write":
case "read":
case "pause":
case "resume":
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
default: {
const ref = stream.session !== undefined
? stream.session[kSocket]
: stream;
const value = ref[prop];
return typeof value === "function"
? FunctionPrototypeBind(value, ref)
: value;
}
}
},
getPrototypeOf(stream) {
if (stream.session !== undefined) {
return ReflectGetPrototypeOf(stream.session[kSocket]);
}
return ReflectGetPrototypeOf(stream);
},
set(stream, prop, value) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Branch on protocol: for http1 use socket APIs, for h2 use stream/response APIs (stream.setTimeout, response.write) and never touch the socket
- For pause/resume semantics use the stream's own pause()/resume() or stop reading the request stream
- Use the proxy's allowed passthrough members (alpnProtocol, remoteAddress, encrypted) instead of read/write for socket introspection
Example fix
// before
server.on("request", (req, res) => {
req.socket.pause(); // ERR_HTTP2_NO_SOCKET_MANIPULATION under h2
setTimeout(() => req.socket.resume(), 100);
});
// after
server.on("request", (req, res) => {
if (!req.httpVersion.startsWith("2.")) {
req.socket.pause();
setTimeout(() => req.socket.resume(), 100);
}
// h2: flow control is per-stream; do not touch the socket
}); Defensive patterns
Strategy: type-guard
Validate before calling
const isHttp2 = (req) => req.httpVersion.startsWith("2.");
if (!isHttp2(req)) {
req.socket.pause();
setTimeout(() => req.socket.resume(), 100);
}
// h2: operate on req.stream / response instead Type guard
function isHttp2Connection(req: http.IncomingMessage): boolean {
return req.httpVersion.startsWith("2.");
} Try / catch
try {
req.socket.pause();
} catch (err) {
if (err.code === "ERR_HTTP2_NO_SOCKET_MANIPULATION") {
// h2 stream socket: use stream-level flow control (or nothing) instead
} else throw err;
} Prevention
- Never call write/read/pause/resume on a socket you did not create
- Gate socket operations behind an http1 check in dual-protocol servers
- Use h2 stream/response flow control for backpressure
When it happens
Trigger: http1-style code doing req.socket.pause()/resume() for backpressure; writing raw bytes (e.g. a WebSocket upgrade handshake) through stream.socket.write(); frameworks that feature-detect socket members and accidentally touch write/read/pause/resume on the same accessor.
Common situations: Shared middleware serving both http1 and http2 servers; WebSocket-over-h2 attempts; ported TLS/health-check code that pokes sockets directly.
Related errors
- ERR_HTTP2_NO_SOCKET_MANIPULATION
- ERR_HTTP2_SOCKET_UNBOUND
- ERR_HTTP2_SOCKET_BOUND
- ERR_FS_CP_SOCKET
- ERR_HTTP_SOCKET_ASSIGNED
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/c4543b1ee70bafaa.
Report an issue: GitHub.