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

  1. Drop the pre-acquired reader and pass the stream untouched to `respondWith`
  2. If you must observe the bytes, read them fully and respond with bytes or a new stream you construct
  3. 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

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


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)