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>} destinationView on GitHub (pinned to 9ad36f7a2c)
Solutions
- Call reader.releaseLock() before pipeThrough().
- For fan-out use const [a, b] = stream.tee() and pipe each branch.
- 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
- Piping acquires the source exclusively; release readers first.
- Use tee() for multiple consumers instead of piping twice.
- Flag getReader() results that are never releaseLock()ed on paths that also pipe.
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
- Target WritableStream is already locked
- ReadableStream is locked
- ReadableStream is locked or disturbed
- Body is unusable
- ReadableStream is locked
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/e8206258b17817d3.
Report an issue: GitHub.