{"record":{"id":"b088393050fd3dc6","repo":"denoland/deno","slug":"readablestream-is-locked-or-disturbed","errorCode":null,"errorMessage":"ReadableStream is locked or disturbed","messagePattern":"ReadableStream is locked or disturbed","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/22_body.js","lineNumber":539,"sourceCode":"    source = TypedArrayPrototypeSlice(object);\n  } else if (isArrayBuffer(object)) {\n    source = TypedArrayPrototypeSlice(new Uint8Array(object));\n  } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) {\n    const res = formDataToBlob(object);\n    stream = res.stream();\n    source = res;\n    length = res.size;\n    contentType = res.type;\n  } else if (\n    ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object)\n  ) {\n    // TODO(@satyarohith): not sure what primordial here.\n    // deno-lint-ignore deno-internal/prefer-primordials\n    source = object.toString();\n    contentType = \"application/x-www-form-urlencoded;charset=UTF-8\";\n  } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) {\n    if (object.locked || isReadableStreamDisturbed(object)) {\n      throw new TypeError(\"ReadableStream is locked or disturbed\");\n    }\n    // Fast path: this stream was materialized from a static body and has not\n    // been read. A common framework pattern (e.g. Hono middleware) is to\n    // reconstruct a response via `new Response(oldResponse.body, oldResponse)`\n    // just to mutate headers. Without recovering the static body, the\n    // reconstructed body would be served through the streaming (chunked) path,\n    // losing Content-Length and the single-write fast response op. Recover the\n    // original static body so the fast path is preserved.\n    //\n    // Only recover when the resulting length matches the original body's\n    // known-length semantics: a string source's byte length is genuinely known\n    // (just deferred to avoid an eager encode), and a Uint8Array source is only\n    // known-length if `staticBodyLength` was recorded for it. Recovering a\n    // Uint8Array whose length was *unknown* (e.g. a chunked request body the\n    // server buffered) would wrongly synthesize a Content-Length when the body\n    // is later sent, so leave those as a stream.\n    const recoveredSource = WeakMapPrototypeGet(staticBodySource, object);\n    const knownLength = WeakMapPrototypeGet(staticBodyLength, object);","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/fetch/22_body.js#L521-L557","documentation":"extractBody() in ext/fetch/22_body.js runs whenever a Request/Response is constructed from a ReadableStream body. The stream must be neither locked (getReader()/pipeTo active) nor disturbed (already read); otherwise this TypeError is thrown. The source also documents a fast path that recovers the original static body when an unread stream was materialized from one (e.g. Hono's new Response(oldResponse.body, oldResponse) pattern) - but only if the stream is still untouched.","triggerScenarios":"const r = req.body.getReader(); new Response(req.body) after reading; passing a stream that a previous pipeThrough/pipeTo locked; re-wrapping a response body in middleware after it was already consumed.","commonSituations":"Framework middleware that re-creates responses (Hono-style) after touching the body; retry/tee logic that reuses a stream; passing the same stream to two constructors expecting independent reads.","solutions":["Check stream.locked and the source's bodyUsed before constructing: only pass fresh streams","Use tee() when two consumers need the same bytes, or clone() the Response before any read","Consume once into a Uint8Array/string and build new Response objects from the buffer for repeats"],"exampleFix":"// before\nconst reader = req.body.getReader();\nawait reader.read();\nreturn new Response(req.body); // TypeError: locked or disturbed\n\n// after\nconst [a, b] = req.body.tee();\nawait a.getReader().read();\nreturn new Response(b); // b is still usable","handlingStrategy":"validation","validationCode":"function usableStream(stream: ReadableStream): boolean {\n  return !stream.locked; // disturbed streams must be avoided by design: never reuse a read stream\n}\nif (bodyStream instanceof ReadableStream && usableStream(bodyStream)) {\n  const res = new Response(bodyStream);\n}","typeGuard":"function isFreshReadableStream(s: unknown): s is ReadableStream<Uint8Array> {\n  return s instanceof ReadableStream && !s.locked;\n}","tryCatchPattern":"try { return new Response(bodyStream); } catch (e) {\n  if (e instanceof TypeError && e.message.includes(\"locked or disturbed\")) {\n    throw new Error(\"body stream already used - tee() or clone() before first read\");\n  }\n  throw e;\n}","preventionTips":["tee() the stream when two consumers need the same bytes","Never hand the same stream to two Request/Response constructors","In middleware that rewraps bodies, check .locked and source .bodyUsed first"],"tags":["fetch","body","streams","request-construction"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}