denoland/deno · error · TypeError

The BYOB request's buffer has been detached and so cannot…

Error message

The BYOB request's buffer has been detached and so cannot be used as a response

What it means

Thrown by ReadableStreamBYOBRequest.respond(bytesWritten) in Deno's WHATWG Streams implementation (ext/web/06_streams.js). Before forwarding bytesWritten to the stream controller, respond() re-reads the view it holds, extracts its ArrayBuffer, and checks isDetachedBuffer(buffer); if the buffer was detached (ownership moved elsewhere), the response cannot be written and a TypeError aborts the call. This matches the spec requirement that the respond target still be valid memory at response time.

Solutions

  1. Do not transfer or detach the buffer between obtaining controller.byobRequest and calling respond(); keep ownership until respond completes.
  2. If ownership must move, copy the bytes into a fresh Uint8Array and call byobRequest.respondWithNewView(freshView) instead of respond().
  3. Audit every postMessage/structuredClone transfer list for the ArrayBuffer backing the BYOB view.
  4. Always read the current view from byobRequest.view at the moment of use instead of caching a view or buffer from an earlier tick.

Example fix

// before
const view = byobRequest.view;
view.buffer.transfer(); // move memory elsewhere -> buffer detached
byobRequest.respond(bytesWritten); // TypeError: buffer detached

// after
const view = byobRequest.view;
writeInto(view);
byobRequest.respond(bytesWritten); // buffer still attached
Defensive patterns

Strategy: validation

Validate before calling

// Before respond(): a detached ArrayBuffer reports byteLength 0,
// and (ES2024) exposes `detached === true`.
const view = byobRequest.view;
const buf = view && (view.buffer ?? null);
if (buf === null || buf.byteLength === 0 || buf.detached === true) {
  controller.close(); // or recover by enqueuing from a fresh buffer
} else {
  byobRequest.respond(bytesWritten);
}

Type guard

function isRespondableView(req) {
  const v = req.view;
  return v != null && v.byteLength > 0 && v.buffer.byteLength > 0;
}

Try / catch

try {
  byobRequest.respond(n);
} catch (err) {
  if (err instanceof TypeError && /detached/.test(err.message)) {
    // buffer moved: recover with a fresh, owned view
    byobRequest.respondWithNewView(new Uint8Array(ownedCopy));
  } else throw err;
}

Prevention

When it happens

Trigger: Calling byobRequest.respond(n) after detaching the view's buffer: view.buffer.transfer() (ArrayBuffer.prototype.transfer), postMessage(view, [view.buffer]) with the buffer in a transfer list, or structuredClone(buffer, [buffer]). Typical in fetch handlers or Deno.serve response code that serves a body from a BYOB view and moves that memory before responding.

Common situations: Zero-copy pipelines that use ArrayBuffer.prototype.transfer() to hand data to a Worker and then try to respond with the same view; transfer lists that accidentally include the BYOB buffer; caching a view across an await during which another task transfers it.

Related errors


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

Appendix: source

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

      {
        enforceRange: true,
      },
    );

    if (this[_controller] === undefined) {
      throw new TypeError("This BYOB request has been invalidated");
    }

    let buffer, byteLength;
    if (isTypedArray(this[_view])) {
      buffer = TypedArrayPrototypeGetBuffer(this[_view]);
      byteLength = TypedArrayPrototypeGetByteLength(this[_view]);
    } else {
      buffer = DataViewPrototypeGetBuffer(this[_view]);
      byteLength = DataViewPrototypeGetByteLength(this[_view]);
    }
    if (isDetachedBuffer(buffer)) {
      throw new TypeError(
        "The BYOB request's buffer has been detached and so cannot be used as a response",
      );
    }
    assert(byteLength > 0);
    assert(getArrayBufferByteLength(buffer) > 0);
    readableByteStreamControllerRespond(this[_controller], bytesWritten);
  }

  respondWithNewView(view) {
    webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype);
    const prefix =
      "Failed to execute 'respondWithNewView' on 'ReadableStreamBYOBRequest'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    view = webidl.converters.ArrayBufferView(view, prefix, "Argument 1");

    if (this[_controller] === undefined) {
      throw new TypeError("This BYOB request has been invalidated");
    }

View on GitHub (pinned to 9ad36f7a2c)