denoland/deno · error · TypeError
Body is unusable
Error message
Body is unusable
What it means
After accepting the Response, Deno inspects its body: if a body exists (innerResp.body !== null) and body.unusable() returns true, the response is rejected. A body becomes unusable when its stream is already disturbed/locked/closed - i.e. the ReadableStream was read, cancelled, teed, or locked by a getReader() call before respondWith got it.
Source
Thrown at ext/http/01_http.js:225
// that don't carry the internal slot (e.g. `Object.create(Response.prototype)`
// or a polyfilled/foreign-realm Response). Reject those here instead of
// crashing later on `innerResp.body`. Mirrors the Deno.serve guard
// added in #34416.
const innerResp = toInnerResponse(resp);
if (innerResp === undefined) {
throw new TypeError(
"First argument to 'respondWith' must be a Response constructed via the Response constructor in this realm",
);
}
// If response body length is known, it will be sent synchronously in a
// single op, in other case a "response body" resource will be created and
// we'll be streaming it.
/** @type {ReadableStream<Uint8Array> | Uint8Array | null} */
let respBody = null;
if (innerResp.body !== null) {
if (innerResp.body.unusable()) {
throw new TypeError("Body is unusable");
}
if (
ObjectPrototypeIsPrototypeOf(
ReadableStreamPrototype,
innerResp.body.streamOrStatic,
)
) {
if (
innerResp.body.length === null ||
ObjectPrototypeIsPrototypeOf(
BlobPrototype,
innerResp.body.source,
)
) {
respBody = innerResp.body.stream;
} else {
const reader = innerResp.body.stream.getReader();
const r1 = await reader.read();View on GitHub (pinned to 89f33cbef2)
Solutions
- Do not read the body before responding; respond with the original Response untouched.
- If you must inspect the body, clone first: const copy = response.clone(); log(await copy.text()); httpConn.respondWith(response).
- Cache the body payload (string/ArrayBuffer) and construct a fresh new Response per request instead of caching the Response object.
- Ensure only one respondWith/reader ever consumes a given response body (guard against double-fire error handlers).
Example fix
// before const res = new Response(data); console.log(await res.text()); // body now disturbed httpConn.respondWith(res); // after const res = new Response(data); const copy = res.clone(); console.log(await copy.text()); httpConn.respondWith(res);
Defensive patterns
Strategy: type-guard
Validate before calling
function isUsableResponse(resp: Response): boolean { return resp.body === null || !resp.body.locked; } Type guard
function isSendableResponse(r: unknown): r is Response { return r instanceof Response && (r.body === null || !r.body.locked); } Try / catch
try { await httpConn.respondWith(resp); } catch (e) { if (e instanceof TypeError && e.message === "Body is unusable") { await httpConn.respondWith(new Response("body consumed", { status: 500 })); return; } throw e; } Prevention
- Clone before reading for logs: const copy = resp.clone(); await copy.text().
- Cache body payloads, not Response objects; build a fresh Response per request.
- Never read/tee a response body that will be handed to respondWith.
When it happens
Trigger: Calling response.json()/text() (or cloning then reading one clone) before httpConn.respondWith(response); locking the body via response.body.getReader() for logging/teeing; returning the same Response object twice (e.g. a cached Response reused across requests); reading the body of a stream-backed Response built from a consumed stream.
Common situations: Access-logging middleware that peeks at bodies; caching a single Response instance and replaying it; race between a background reader and the responder; double-respond in error paths (respondWith the same object after a timeout also responded).
Related errors
- First argument to 'respondWith' must be a Response or a prom
- First argument to 'respondWith' must be a Response construct
- ReadableStream is locked
- Response body is already used
- Invalid header name: "${name}"
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/05e390c1df8c3088.
Report an issue: GitHub.