{"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/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/fetch/22_body.js#L521-L557","documentation":"Thrown in extractBody() (ext/fetch/22_body.js) when a ReadableStream is passed as BodyInit (to fetch(), Request, or Response) but the stream is already locked (object.locked — has an active reader) or disturbed (read or piped before). A locked/disturbed stream cannot be re-transferred as a body, so the constructor refuses it up front.","triggerScenarios":"`const reader = stream.getReader(); fetch(url, { body: stream })`; using a stream after `for await` over it; passing the same stream to two Requests; passing a stream returned by res.body that was already read. Note: res.body of a network response counts as disturbed once read.","commonSituations":"Reading a response body for inspection and then trying to forward the same stream in a new fetch/Response; tee-ing incorrectly (forgetting that a reader lock persists until releaseLock); retry logic that reuses the original stream on a second attempt.","solutions":["Never attach a reader to a stream you intend to send as a body — pass the stream itself unread.","If you must inspect, branch it: `const [a, b] = stream.tee()` — read from a, send b.","For retries, buffer the body (arrayBuffer/text) so each attempt creates a fresh stream."],"exampleFix":"// before\nconst reader = upstream.body.getReader(); // locks stream\nawait fetch(target, { method: \"POST\", body: upstream.body }); // throws\n\n// after\nconst [inspect, forward] = upstream.body.tee();\ninspect.cancel(); // or read for logging\nawait fetch(target, { method: \"POST\", body: forward });","handlingStrategy":"type-guard","validationCode":"function usableAsStreamBody(s) {\n  return s instanceof ReadableStream && !s.locked; // disturbed state must be tracked by the caller\n}\nif (!usableAsStreamBody(stream)) throw new Error(\"stream is locked or already read\");","typeGuard":"function isSendableBodyStream(s: unknown): s is ReadableStream {\n  return s instanceof ReadableStream && !s.locked && !isDisturbed(s);\n}\n// practical guard: also track your own reads\nfunction isDisturbed(s: ReadableStream): boolean {\n  return s.locked; // after releaseLock, track disturbance via a wrapper flag\n}","tryCatchPattern":null,"preventionTips":["Pass streams unread; never getReader() on a stream destined to be a body.","Use stream.tee() to branch inspection and forwarding.","Buffer bodies (text/arrayBuffer) when retries will re-send them."],"tags":["fetch","readable-stream","body","stream-lock"],"backgroundTag":"readable-stream-locked","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}