denoland/deno · error · TypeError

Target WritableStream is already locked

Error message

Target WritableStream is already locked

What it means

pipeThrough(transform, options) found transform.writable already locked — another writer or an active pipeTo owns it. pipeThrough must write into the transform's writable side, which requires acquiring it exclusively, so isWritableStreamLocked(writable) === true is rejected.

Source

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

    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
   * @param {PipeOptions=} options
   * @returns {Promise<void>}
   */

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Release any writer on transform.writable (writer.releaseLock()) before piping.
  2. Give each source its own TransformStream instance; create N transforms for N sources.
  3. Await or cancel the previous pipe before reusing a transform.

Example fix

// before
const ts = new TransformStream({});
const w = ts.writable.getWriter();
await w.write(header);
source.pipeThrough(ts); // TypeError: Target WritableStream is already locked

// after
const ts = new TransformStream({});
const w = ts.writable.getWriter();
await w.write(header);
w.releaseLock();
source.pipeThrough(ts); // ok
Defensive patterns

Strategy: validation

Validate before calling

if (transform.writable.locked) {
  throw new Error('transform writable locked — release its writer before piping');
}
source.pipeThrough(transform);

Try / catch

try {
  source.pipeThrough(transform);
} catch (e) {
  if (e instanceof TypeError && e.message === 'Target WritableStream is already locked') {
    headerWriter.releaseLock();
    source.pipeThrough(transform); // retry once unlocked
  } else throw e;
}

Prevention

When it happens

Trigger: transform.writable.getWriter() held (not released) when pipeThrough runs; the same TransformStream already fed by another pipeTo/pipeThrough; calling pipeThrough twice through one transform object.

Common situations: Reusing a single TransformStream across multiple sources; a wrapper pre-writing a header via getWriter() before handing the transform to pipeThrough; concurrent pipeline setup racing on a shared transform.

Related errors


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