denoland/deno · error · RangeError

ERR_HTTP2_PING_LENGTH

ERR_HTTP2_PING_LENGTH

Error message

HTTP2 ping payload must be 8 bytes

What it means

HTTP/2 PING frames carry exactly 8 bytes of opaque payload, so session.ping(payload, cb) throws ERR_HTTP2_PING_LENGTH when payload.byteLength !== 8. The check deliberately uses byteLength, not length, so a Uint16Array view with 4 elements (8 bytes) is accepted while a 9-byte Buffer is not, matching Node's native validation.

Source

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

  // been called, the ping callback will be invoked immediately with a ping
  // cancelled error and a duration of 0.0.
  ping(payload, callback) {
    if (this.destroyed) {
      throw new ERR_HTTP2_INVALID_SESSION();
    }

    if (typeof payload === "function") {
      callback = payload;
      payload = undefined;
    }
    if (payload) {
      validateBuffer(payload, "payload");
    }
    // Use byteLength rather than length so non-Uint8Array views (e.g. a
    // Uint16Array of 4 elements = 8 bytes) are correctly accepted, matching
    // Node's Http2Session#ping which validates the underlying byte length.
    if (payload && payload.byteLength !== 8) {
      throw new ERR_HTTP2_PING_LENGTH();
    }
    validateFunction(callback, "callback");

    // Allocate an HTTP2PING async resource so async_hooks observers see
    // init/before/after/destroy for each ping, matching Node's Http2Ping
    // AsyncWrap in src/node_http2.cc.
    const userCb = pingCallback(callback);
    const asyncResource = new AsyncResource("HTTP2PING");
    const cb = (ack, duration, ackPayload) => {
      try {
        asyncResource.runInAsyncScope(
          userCb,
          this,
          ack,
          duration,
          ackPayload,
        );
      } finally {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Omit the payload entirely and just call session.ping(callback) — Node fills zeros
  2. Use crypto.randomBytes(8) or Buffer.alloc(8) for a well-formed payload
  3. Pre-check payload && payload.byteLength !== 8 and fix or reject before calling

Example fix

// before
session.ping(Buffer.from(JSON.stringify({ t: Date.now() })), cb);

// after
session.ping(cb); // or: session.ping(crypto.randomBytes(8), cb);
Defensive patterns

Strategy: validation

Validate before calling

const payload = makePayload(); // user data
if (payload !== undefined && payload.byteLength !== 8) {
  throw new RangeError(`ping payload must be 8 bytes, got ${payload.byteLength}`);
}
session.ping(payload, callback);

Type guard

function isValidPingPayload(p) {
  return p === undefined || (ArrayBuffer.isView(p) && p.byteLength === 8);
}

Prevention

When it happens

Trigger: session.ping(Buffer.from('123456789')) (9 bytes), ping(Buffer.alloc(0)), ping(new Uint8Array(16)), or any TypedArray/Buffer whose underlying byte length is not exactly 8.

Common situations: Using a hex-encoded timestamp string (16 characters = 16 bytes) as the payload; generating 'random' IDs with randomBytes(16) copy-pasted from UUID code; sending an application heartbeat object serialized to JSON of arbitrary length.

Related errors


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