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

  1. Branch on protocol: for http1 use socket APIs, for h2 use stream/response APIs (stream.setTimeout, response.write) and never touch the socket
  2. For pause/resume semantics use the stream's own pause()/resume() or stop reading the request stream
  3. 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

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


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