Skip to content

Dead code in writeBlob: assert and RequestContentLengthMismatchError are both unreachable in practice #5153

@trivikr

Description

@trivikr

Bug Description

In both lib/dispatcher/client-h1.js and lib/dispatcher/client-h2.js, the writeBlob function contains an assert and a RequestContentLengthMismatchError guard that are both dead code — neither can ever trigger under any reachable code path.

assert(contentLength === body.size, 'blob body must have content length')
try {
if (contentLength != null && contentLength !== body.size) {
throw new RequestContentLengthMismatchError()
}

The contentLength parameter passed to writeBlob is computed in writeH1/writeH2 as follows:

const bodyLength = util.bodyLength(body)
contentLength = bodyLength ?? contentLength

Since util.bodyLength returns body.size for blob-like bodies (never null), the local contentLength variable is always overwritten to body.size before writeBlob is called. This means:

  1. The assert(contentLength === body.size) is always true — it can never fire.
  2. The if (contentLength != null && contentLength !== body.size) is always false — RequestContentLengthMismatchError can never be thrown from this path.

Meanwhile, if a user provides a mismatched content-length header, it is caught earlier in writeH1/writeH2 by the shouldSendContentLength check (which compares request.contentLength against the computed contentLength), so the mismatch never propagates to writeBlob.

The result is that blob bodies have no graceful RequestContentLengthMismatchError path inside writeBlob — the error handling code exists but is unreachable.

Reproducible By

This script demonstrates that the mismatch is caught before writeBlob, not inside it:

import { Client } from "undici";
import { once } from "node:events";
import { createServer } from "node:http";

const server = createServer((req, res) => {
  req.resume();
  req.on("end", () => res.end("ok"));
});

server.listen(0);
await once(server, "listening");

const client = new Client(`http://localhost:${server.address().port}`);
const blob = new Blob(["hello"], { type: "text/plain" }); // size = 5

try {
  await client.request({
    path: "/",
    method: "POST",
    headers: {
      "content-length": 999, // intentionally wrong
    },
    body: blob,
  });
} catch (err) {
  // Error is thrown from writeH1's early check, NOT from writeBlob
  console.log("Caught error:", err.constructor.name, err.message);
} finally {
  await client.close();
  server.close();
}

Expected Behavior

The dead code in writeBlob should be cleaned up. Either:

  1. Remove both the assert and the if guard — they are redundant since the mismatch is already handled upstream in writeH1/writeH2.
  2. Remove the assert and keep the if guard as a defensive safety net, in case future refactors change how contentLength is computed before reaching writeBlob.

Logs & Screenshots

Caught error: RequestContentLengthMismatchError Request body length does not match content-length header

Environment

macOS 26.4.1
Node v24.15.0
undici v8.1.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions