{"record":{"id":"6398a7af01d04248","repo":"denoland/deno","slug":"readablestream-is-locked","errorCode":null,"errorMessage":"ReadableStream is locked","messagePattern":"ReadableStream is locked","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/01_http.js","lineNumber":300,"sourceCode":"        ) {\n          await respBody.cancel(error);\n        }\n        throw error;\n      }\n\n      if (isStreamingResponseBody) {\n        let success = false;\n        if (\n          respBody === null ||\n          !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody)\n        ) {\n          throw new TypeError(\"Unreachable\");\n        }\n        const resourceBacking = getReadableStreamResourceBacking(respBody);\n        let reader;\n        if (resourceBacking) {\n          if (respBody.locked) {\n            throw new TypeError(\"ReadableStream is locked\");\n          }\n          reader = respBody.getReader(); // Acquire JS lock.\n          try {\n            await op_http_write_resource(\n              writeStreamRid,\n              resourceBacking.rid,\n            );\n            if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);\n            readableStreamClose(respBody); // Release JS lock.\n            success = true;\n          } catch (error) {\n            const connError = httpConn[connErrorSymbol];\n            if (\n              ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&\n              connError != null\n            ) {\n              // deno-lint-ignore no-ex-assign\n              error = new connError.constructor(connError.message);","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/http/01_http.js#L282-L318","documentation":"When the response body is a resource-backed ReadableStream (one wrapping an OS resource rid, e.g. a file or socket sideload), `respondWith` transfers ownership by acquiring the stream's single reader and handing the resource to the HTTP layer. If your code already locked the stream (`getReader()`, an active `pipeTo`/`pipeThrough`), the lock cannot be acquired and this TypeError is thrown.","triggerScenarios":"Calling `resp.body.getReader()` (e.g. for progress reporting) before passing the Response to `respondWith`; passing a stream that is mid-`pipeTo`; wrappers that hold a reader open.","commonSituations":"Streaming file/socket bodies with side-channel monitoring; reusing one stream both for custom reads and as the response body; libraries that defensively lock streams on creation.","solutions":["Drop the pre-acquired reader and pass the stream untouched to `respondWith`","If you must observe the bytes, read them fully and respond with bytes or a new stream you construct","Use `tee()` only for in-memory streams — splitting a resource-backed stream loses its resource backing"],"exampleFix":"// before\nconst reader = resp.body!.getReader(); // progress tracking\nawait e.respondWith(resp); // ReadableStream is locked\n\n// after\n// let respondWith consume the stream; report progress via Content-Length on the client\nawait e.respondWith(resp);","handlingStrategy":"validation","validationCode":"if (resp.body instanceof ReadableStream && resp.body.locked) {\n  throw new Error(\"release the stream reader before responding\");\n}\nawait e.respondWith(resp);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call getReader()/pipeTo() on a stream you intend to hand to respondWith","Do progress tracking via Content-Length on the receiving side, not by tapping the body stream","Remember resource-backed streams (file/socket) can only be transferred once, untouched"],"tags":["http","respondwith","readable-stream","streaming"],"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"}