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
- Enqueue a copy (controller.enqueue(view.slice())) when the original buffer must be reused or transferred elsewhere.
- Never put a buffer on a postMessage/structuredClone transfer list while a stream still owns views into it.
- Treat any view passed to enqueue() as consumed — never enqueue or write it again.
- 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
- Treat any view passed to enqueue()/respond() as transferred: use it exactly once.
- Copy (slice) before enqueue when the buffer is shared, pooled, or destined for a transfer list.
- After ArrayBuffer.transfer(), only the returned new buffer is usable.
- Check chunk.buffer.detached in debug logs when binary pipelines misbehave.
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
- The BYOB request's buffer has been detached and so cannot be
- The BYOB request's buffer has been detached and so cannot be
- The buffer of view has different capacity than byobRequest
- "autoAllocateChunkSize" must be greater than 0
- Cannot use a BYOB reader with a non-byte stream
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/c7323f18835d2bd9.
Report an issue: GitHub.