denoland/deno · error · TypeError

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

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.

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)

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.