denoland/deno · error · Error

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

Http2Session exposes its socket through a proxy whose get trap throws ERR_HTTP2_NO_SOCKET_MANIPULATION for destroy, emit, end, pause, read, resume, write, setEncoding, setKeepAlive, and setNoDelay. HTTP/2 multiplexes many streams over a single connection, so direct reads/writes or lifecycle calls on the raw socket would corrupt the binary framing for every stream; only session-level APIs are legal.

Source

Thrown at ext/node/polyfills/http2.ts:807

const proxySocketHandler = {
  get(session, prop) {
    switch (prop) {
      case "setTimeout":
      case "ref":
      case "unref":
        return FunctionPrototypeBind(session[prop], session);
      case "destroy":
      case "emit":
      case "end":
      case "pause":
      case "read":
      case "resume":
      case "write":
      case "setEncoding":
      case "setKeepAlive":
      case "setNoDelay":
        throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
      default: {
        const socket = session[kSocket];
        if (socket === undefined) {
          throw new ERR_HTTP2_SOCKET_UNBOUND();
        }
        const value = socket[prop];
        return typeof value === "function"
          ? FunctionPrototypeBind(value, socket)
          : value;
      }
    }
  },
  getPrototypeOf(session) {
    const socket = session[kSocket];
    if (socket === undefined) {
      throw new ERR_HTTP2_SOCKET_UNBOUND();
    }
    return ReflectGetPrototypeOf(socket);

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Use the session-level equivalents: session.destroy(), session.setTimeout(), session.close() instead of socket methods
  2. Branch on protocol before touching the socket: if (req.httpVersion === '2.0' || session.constructor.name === 'Http2Session') skip socket manipulation
  3. Operate on the individual Http2Stream (readable/writable sides) for data flow instead of the socket

Example fix

// before
session.socket.setNoDelay(true); // throws ERR_HTTP2_NO_SOCKET_MANIPULATION
session.socket.destroy();

// after
session.setTimeout(5000); // allowed through the proxy
session.destroy(); // session-level lifecycle API
Defensive patterns

Strategy: type-guard

Validate before calling

const isHttp2 = (s: { constructor: { name: string } }) =>
  s.constructor.name === 'Http2Session';
if (!isHttp2(session)) session.socket.setNoDelay(true); // http1 only

Type guard

import type { Http2Session } from 'node:http2';
import type { Socket } from 'node:net';
const isHttp2Session = (s: Http2Session | { socket: Socket }): s is Http2Session =>
  (s as Http2Session).alpnProtocol !== undefined &&
  typeof (s as Http2Session).goaway === 'number';

Try / catch

try {
  session.socket.write(chunk);
} catch (err) {
  if ((err as NodeJS.ErrnoException).code === 'ERR_HTTP2_NO_SOCKET_MANIPULATION') {
    // route the data through the Http2Stream / session APIs instead
  } else throw err;
}

Prevention

When it happens

Trigger: session.socket.write('x'); session.socket.destroy(); session.socket.pause() / resume(); session.socket.read(); session.socket.setEncoding('utf8'); session.socket.setNoDelay(true); session.socket.emit('drain'). Accessing these through the proxy (get trap, then invoking) triggers the throw.

Common situations: Middleware written for HTTP/1 that tunes sockets (setNoDelay, setKeepAlive) applied to an http2 server; code sniffing or writing raw bytes for protocol detection; test harnesses that pause/resume the socket to simulate backpressure; libraries that call socket.destroy() for cleanup on shutdown.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/c5291ad559e52724. Report an issue: GitHub.