denoland/deno · error · TypeError
Body is unusable
Error message
Body is unusable
What it means
Thrown by Response.clone() when the response's body stream has already been disturbed (read, piped, cancelled, or locked by a reader) or has errored. The fetch spec requires clone() to tee an untouched stream so both copies stay readable, so a used body cannot be cloned. Deno first materializes lazy static bodies (responses created from strings/ArrayBuffers), so this only fires for genuinely consumed or errored streams.
Source
Thrown at ext/fetch/23_response.js:841
return this[_response].statusMessage;
}
/**
* @returns {Headers}
*/
get headers() {
webidl.assertBranded(this, ResponsePrototype);
return responseHeaders(this);
}
/**
* @returns {Response}
*/
clone() {
webidl.assertBranded(this, ResponsePrototype);
materializeLazyStaticBody(this);
if (this[_body] && this[_body].unusable()) {
throw new TypeError("Body is unusable");
}
const second = webidl.createBranded(Response);
const newRes = cloneInnerResponse(this[_response]);
initializeResponseBase(second, newRes, responseHeaderGuard(this));
return second;
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this),
keys: [
"body",
"bodyUsed",
"headers",
"ok",
"redirected",View on GitHub (pinned to 89f33cbef2)
Solutions
- Clone immediately after fetch() resolves, before any read: const copy = res.clone()
- Check res.bodyUsed === false before calling clone()
- If the body is already consumed, re-issue the fetch instead of cloning
- For manual splitting of a partially read stream, use res.body.tee() on a still-locked-free stream instead of clone()
Example fix
// before const data = await res.json(); const copy = res.clone(); // TypeError: Body is unusable // after const copy = res.clone(); // clone before any read const data = await res.json();
Defensive patterns
Strategy: validation
Validate before calling
function isCloneable(res) {
return !res.bodyUsed && (!res.body || !res.body.locked);
}
// usage
if (isCloneable(res)) {
const copy = res.clone();
} else {
// body already consumed: refetch or reuse parsed data
} Type guard
function isCloneable(res: Response): boolean {
return !res.bodyUsed && (res.body === null || !res.body.locked);
} Try / catch
try {
const copy = res.clone();
} catch (err) {
if (err instanceof TypeError && err.message === "Body is unusable") {
// body already consumed; refetch or reuse the parsed value
} else throw err;
} Prevention
- Clone immediately after fetch resolves, before passing the Response anywhere
- Check res.bodyUsed before clone() in wrappers and middleware
- Parse once and pass the parsed value to consumers instead of sharing a Response
- Remember res.body.getReader() locks the body even before the first read
When it happens
Trigger: Calling res.text(), res.json(), res.arrayBuffer(), res.formData(), res.body.getReader().read(), or res.body.cancel() on a Response and then calling res.clone() on the same Response; or cloning a streaming response whose body errored mid-transfer.
Common situations: Logging or caching middleware that parses the body first and tries to clone afterwards; retry wrappers that clone after inspection; interceptor chains where an earlier layer already consumed the body; tests that await the body twice.
Related errors
- Body is unusable
- Input request's body is unusable
- Response with null body status cannot have body
- Response body is already used
- Cannot change header: headers are immutable
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/934eec92258a64db.
Report an issue: GitHub.