denoland/deno · error · TypeError
ReadableStream is locked
Error message
ReadableStream is locked
What it means
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.
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
Example fix
// before const reader = resp.body!.getReader(); // progress tracking await e.respondWith(resp); // ReadableStream is locked // after // let respondWith consume the stream; report progress via Content-Length on the client await e.respondWith(resp);
Defensive patterns
Strategy: validation
Validate before calling
if (resp.body instanceof ReadableStream && resp.body.locked) {
throw new Error("release the stream reader before responding");
}
await e.respondWith(resp); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Body is unusable
- First argument to 'respondWith' must be a Response or a…
- First argument to 'respondWith' must be a Response…
- Already closed
- Already upgraded
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/6398a7af01d04248.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/01_http.js:300
) {
await respBody.cancel(error);
}
throw error;
}
if (isStreamingResponseBody) {
let success = false;
if (
respBody === null ||
!ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody)
) {
throw new TypeError("Unreachable");
}
const resourceBacking = getReadableStreamResourceBacking(respBody);
let reader;
if (resourceBacking) {
if (respBody.locked) {
throw new TypeError("ReadableStream is locked");
}
reader = respBody.getReader(); // Acquire JS lock.
try {
await op_http_write_resource(
writeStreamRid,
resourceBacking.rid,
);
if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid);
readableStreamClose(respBody); // Release JS lock.
success = true;
} catch (error) {
const connError = httpConn[connErrorSymbol];
if (
ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) &&
connError != null
) {
// deno-lint-ignore no-ex-assign
error = new connError.constructor(connError.message);View on GitHub (pinned to 9ad36f7a2c)