denoland/deno · error · TypeError

Chunk's buffer is detached and so cannot be enqueued

Error message

Chunk's buffer is detached and so cannot be enqueued

What it means

Thrown by ReadableByteStreamController.enqueue() on a byte stream (new ReadableStream({ type: 'bytes' })) when the chunk is a TypedArray or DataView whose backing ArrayBuffer is detached. Enqueue on a byte stream takes ownership of the chunk's buffer by transferring it (ArrayBufferPrototypeTransferToFixedLength) into the stream's queue, which is impossible once the buffer is detached. Buffers become detached via postMessage/structuredClone transfer lists, ArrayBuffer.prototype.transfer()/transferToFixedLength(), and similar move operations.

Source

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

    buffer = TypedArrayPrototypeGetBuffer(/** @type {Uint8Array}} */ (chunk));
    byteLength = TypedArrayPrototypeGetByteLength(
      /** @type {Uint8Array} */ (chunk),
    );
    byteOffset = TypedArrayPrototypeGetByteOffset(
      /** @type {Uint8Array} */ (chunk),
    );
  } else {
    buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (chunk));
    byteLength = DataViewPrototypeGetByteLength(
      /** @type {DataView} */ (chunk),
    );
    byteOffset = DataViewPrototypeGetByteOffset(
      /** @type {DataView} */ (chunk),
    );
  }

  if (isDetachedBuffer(buffer)) {
    throw new TypeError(
      "Chunk's buffer is detached and so cannot be enqueued",
    );
  }
  const transferredBuffer = ArrayBufferPrototypeTransferToFixedLength(buffer);
  if (controller[_pendingPullIntos].size !== 0) {
    const firstPendingPullInto = controller[_pendingPullIntos].peek();
    // deno-lint-ignore deno-internal/prefer-primordials
    if (isDetachedBuffer(firstPendingPullInto.buffer)) {
      throw new TypeError(
        "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk",
      );
    }
    readableByteStreamControllerInvalidateBYOBRequest(controller);
    firstPendingPullInto.buffer = ArrayBufferPrototypeTransferToFixedLength(
      // deno-lint-ignore deno-internal/prefer-primordials
      firstPendingPullInto.buffer,
    );
    if (firstPendingPullInto.readerType === "none") {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Enqueue a copy (controller.enqueue(view.slice())) when the original buffer must be reused or transferred elsewhere.
  2. Never put a buffer on a postMessage/structuredClone transfer list while a stream still owns views into it.
  3. Treat any view passed to enqueue() as consumed — never enqueue or write it again.
  4. Guard before enqueue: if (chunk.buffer.detached === true) rebuild the chunk from a retained copy.

Example fix

// before
const view = new Uint8Array(buf);
worker.postMessage(buf, [buf]); // detaches buf
controller.enqueue(view);        // TypeError: Chunk's buffer is detached

// after
const view = new Uint8Array(buf);
controller.enqueue(view.slice()); // stream takes the copy
worker.postMessage(buf, [buf]);   // safe to transfer the original
Defensive patterns

Strategy: validation

Validate before calling

// run before controller.enqueue(chunk) on a byte stream
function ensureEnqueueable(chunk) {
  if (chunk.buffer.detached === true) {
    throw new Error('chunk buffer is detached — enqueue a retained copy instead');
  }
}
ensureEnqueueable(chunk);
controller.enqueue(chunk);

Type guard

function hasLiveBuffer(view) {
  return (view instanceof Uint8Array || view instanceof DataView)
    && view.buffer.detached !== true;
}

Try / catch

try {
  controller.enqueue(chunk);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('detached')) {
    controller.error(e); // the data is gone; fail the stream loudly
  }
  throw e;
}

Prevention

When it happens

Trigger: controller.enqueue(view) after worker.postMessage(view.buffer, [view.buffer]); enqueueing a view whose buffer was already moved with buf.transfer(); enqueueing the same view twice (the first enqueue transfers and detaches it); enqueueing a view whose buffer a previous respond()/respondWithNewView() already consumed.

Common situations: Zero-copy pipelines that both enqueue into a ReadableStream and ship buffers to a Worker or WASM; buffer pools reused across streams; right-sizing buffers with transfer() before enqueue; porting Node Buffer code where enqueue does not transfer ownership.

Related errors


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