Hmbown/CodeWhale · error · BodyReadError
invalid Content-Length
Error message
invalid Content-Length
What it means
BodyReadError with status 400 raised by readBoundedBody's reject() helper when the request's Content-Length header is missing or not a valid integer. Content-Length is used only as an early rejection signal; a malformed value means the HTTP request itself is malformed, so reading is aborted and the stream cancelled before any bytes are consumed.
Solutions
- Fix the client to send a correct decimal byte count for Content-Length, or omit it and use chunked transfer encoding.
- Remove stray characters/units from the manually set Content-Length header.
- If behind a proxy, check whether the proxy mangles the header and correct its configuration.
Example fix
// before curl -H 'Content-Length: 12 bytes' -d 'token=x' https://host/api/admin/login // after curl -d 'token=x' https://host/api/admin/login
Defensive patterns
Strategy: validation
Validate before calling
const cl = request.headers.get("content-length");
if (cl !== null && !/^\d+$/.test(cl)) throw new Error("client sent invalid Content-Length"); Try / catch
try {
const body = await readBoundedBody(request, MAX);
} catch (err) {
if (err instanceof BodyReadError && err.status === 400 && err.message === "invalid Content-Length") {
return new Response("invalid Content-Length header", { status: 400 });
}
throw err;
} Prevention
- Let the HTTP client compute Content-Length automatically instead of setting it by hand.
- Never include units, signs, or whitespace in Content-Length values.
- Prefer chunked transfer encoding for streaming bodies with unknown length.
When it happens
Trigger: A client sends a Content-Length header that is not all digits — e.g. `"abc"`, negative values, or a header containing whitespace/units — to any route using readBoundedBody (such as the admin form POST).
Common situations: Hand-rolled HTTP clients or curl scripts with a malformed -H 'Content-Length: ...'; misbehaving proxies rewriting the header; fuzzed or hostile traffic (the check is intentional hardening).
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- header name cannot be empty
- header name cannot be empty
- expected application/x-www-form-urlencoded
- invalid header pair ' ', expected name=value
- MCP HTTP requires an http:// or https:// URL with a host
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/1c74d3c72aa9e99e.
Report an issue: GitHub.
Appendix: source
Thrown at web/lib/bounded-body.ts:12
export class BodyReadError extends Error {
constructor(readonly status: 400 | 413, message: string) {
super(message);
this.name = "BodyReadError";
}
}
/** Count raw bytes while reading; Content-Length is only an early rejection. */
export async function readBoundedBody(request: { body: ReadableStream<Uint8Array> | null; headers: Headers }, maxBytes: number): Promise<Uint8Array> {
const reject = async (status: 400 | 413, message: string): Promise<never> => {
try { await request.body?.cancel(message); } catch { /* Keep the original rejection. */ }
throw new BodyReadError(status, message);
};
const rawLength = request.headers.get("content-length");
if (rawLength !== null) {
if (!/^\d+$/.test(rawLength)) return reject(400, "invalid Content-Length");
if (Number(rawLength) > maxBytes) return reject(413, "payload too large");
}
if (!request.body) return new Uint8Array();
const reader = request.body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maxBytes) throw new BodyReadError(413, "payload too large");
chunks.push(value);View on GitHub (pinned to 433685b202)