{"record":{"id":"05e390c1df8c3088","repo":"denoland/deno","slug":"body-is-unusable-05e390","errorCode":null,"errorMessage":"Body is unusable","messagePattern":"Body is unusable","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/01_http.js","lineNumber":225,"sourceCode":"      // that don't carry the internal slot (e.g. `Object.create(Response.prototype)`\n      // or a polyfilled/foreign-realm Response). Reject those here instead of\n      // crashing later on `innerResp.body`. Mirrors the Deno.serve guard\n      // added in #34416.\n      const innerResp = toInnerResponse(resp);\n      if (innerResp === undefined) {\n        throw new TypeError(\n          \"First argument to 'respondWith' must be a Response constructed via the Response constructor in this realm\",\n        );\n      }\n\n      // If response body length is known, it will be sent synchronously in a\n      // single op, in other case a \"response body\" resource will be created and\n      // we'll be streaming it.\n      /** @type {ReadableStream<Uint8Array> | Uint8Array | null} */\n      let respBody = null;\n      if (innerResp.body !== null) {\n        if (innerResp.body.unusable()) {\n          throw new TypeError(\"Body is unusable\");\n        }\n        if (\n          ObjectPrototypeIsPrototypeOf(\n            ReadableStreamPrototype,\n            innerResp.body.streamOrStatic,\n          )\n        ) {\n          if (\n            innerResp.body.length === null ||\n            ObjectPrototypeIsPrototypeOf(\n              BlobPrototype,\n              innerResp.body.source,\n            )\n          ) {\n            respBody = innerResp.body.stream;\n          } else {\n            const reader = innerResp.body.stream.getReader();\n            const r1 = await reader.read();","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/01_http.js#L207-L243","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\nconst res = new Response(data);\nconsole.log(await res.text()); // body now disturbed\nhttpConn.respondWith(res);\n\n// after\nconst res = new Response(data);\nconst copy = res.clone();\nconsole.log(await copy.text());\nhttpConn.respondWith(res);","handlingStrategy":"type-guard","validationCode":"function isUsableResponse(resp: Response): boolean { return resp.body === null || !resp.body.locked; }","typeGuard":"function isSendableResponse(r: unknown): r is Response { return r instanceof Response && (r.body === null || !r.body.locked); }","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["http","streams","response-body","servehttp"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}