denoland/deno · error · TypeError

ReadableStream is already locked

Error message

ReadableStream is already locked

What it means

ReadableStream.pipeThrough(transform, options) was called while the source stream is locked by an existing reader or an active pipe. pipeThrough must attach a reader to the source internally, so this.locked must be false when it is called.

Source

Thrown at ext/web/06_streams.js:6220

   */
  pipeThrough(transform, options = { __proto__: null }) {
    webidl.assertBranded(this, ReadableStreamPrototype);
    const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    transform = webidl.converters.ReadableWritablePair(
      transform,
      prefix,
      "Argument 1",
    );
    options = webidl.converters.StreamPipeOptions(
      options,
      prefix,
      "Argument 2",
    );
    const { readable, writable } = transform;
    const { preventClose, preventAbort, preventCancel, signal } = options;
    if (isReadableStreamLocked(this)) {
      throw new TypeError("ReadableStream is already locked");
    }
    if (isWritableStreamLocked(writable)) {
      throw new TypeError("Target WritableStream is already locked");
    }
    const promise = readableStreamPipeTo(
      this,
      writable,
      preventClose,
      preventAbort,
      preventCancel,
      signal,
    );
    setPromiseIsHandledToTrue(promise);
    return readable;
  }

  /**
   * @param {WritableStream<R>} destination

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Call reader.releaseLock() before pipeThrough().
  2. For fan-out use const [a, b] = stream.tee() and pipe each branch.
  3. Await the first pipe's completion before starting a second pipeline on the same source.

Example fix

// before
const r = stream.getReader();
await peek(r);
stream.pipeThrough(transformer); // TypeError: ReadableStream is already locked

// after
const r = stream.getReader();
await peek(r);
r.releaseLock();
stream.pipeThrough(transformer); // ok
Defensive patterns

Strategy: validation

Validate before calling

if (readable.locked) {
  throw new Error('source locked — release its reader before piping');
}
readable.pipeThrough(transformer);

Try / catch

try {
  readable.pipeThrough(transformer);
} catch (e) {
  if (e instanceof TypeError && e.message === 'ReadableStream is already locked') {
    reader.releaseLock();
    readable.pipeThrough(transformer); // retry once unlocked
  } else throw e;
}

Prevention

When it happens

Trigger: Calling getReader() (even briefly, for sniffing) and then pipeThrough() without releaseLock(); running two pipes on the same source concurrently; async iteration started before piping.

Common situations: Middleware that both reads and pipes; logging the first bytes then piping the rest; attempting fan-out by piping twice instead of using tee().

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/e8206258b17817d3. Report an issue: GitHub.