denoland/deno · error · TypeError

"bytesWritten" must be 0 when calling respond() on a closed

Error message

"bytesWritten" must be 0 when calling respond() on a closed stream: received ${bytesWritten}

What it means

ReadableStreamBYOBRequest.respond() was called with a non-zero bytesWritten while the stream is in the 'closed' state. Once a byte stream is closed there is no pending consumer buffer left to fill, so the spec only permits respond(0) as a no-op acknowledgment. In practice this means code responded after controller.close() already ran.

Source

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

    }
  }
  controller[_pendingPullIntos].enqueue(pullIntoDescriptor);
  readableStreamAddReadIntoRequest(stream, readIntoRequest);
  readableByteStreamControllerCallPullIfNeeded(controller);
}

/**
 * @param {ReadableByteStreamController} controller
 * @param {number} bytesWritten
 * @returns {void}
 */
function readableByteStreamControllerRespond(controller, bytesWritten) {
  assert(controller[_pendingPullIntos].size !== 0);
  const firstDescriptor = controller[_pendingPullIntos].peek();
  const state = controller[_stream][_state];
  if (state === "closed") {
    if (bytesWritten !== 0) {
      throw new TypeError(
        `"bytesWritten" must be 0 when calling respond() on a closed stream: received ${bytesWritten}`,
      );
    }
  } else {
    assert(state === "readable");
    if (bytesWritten === 0) {
      throw new TypeError(
        '"bytesWritten" must be greater than 0 when calling respond() on a readable stream',
      );
    }
    if (
      (firstDescriptor.bytesFilled + bytesWritten) >
        // deno-lint-ignore deno-internal/prefer-primordials
        firstDescriptor.byteLength
    ) {
      throw new RangeError('"bytesWritten" out of range');
    }
  }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Check for zero-byte/EOF reads and call controller.close() instead of responding; never respond with bytes after close.
  2. Fetch controller.byobRequest fresh inside each pull() and respond exactly once, only while it is non-null.
  3. Sequence I/O: await the pending read before closing the stream.
  4. On a possibly-closed stream, respond(0) or skip — never with a byte count.

Example fix

// before
const done = bytesWritten === 0;
if (done) controller.close();
byob.respond(bytesWritten); // done && bytesWritten > 0 -> TypeError

// after
const done = bytesWritten === 0;
if (done) { controller.close(); return; } // EOF: close, do not respond
byob.respond(bytesWritten);
Defensive patterns

Strategy: validation

Validate before calling

// helper: respond safely on a possibly-closed byte stream
function respondSafe(controller, n) {
  const byob = controller.byobRequest;
  if (byob === null) return; // no live request (closed or none pending)
  if (n === 0) return;       // nothing written: not a response
  byob.respond(Math.min(n, byob.view.byteLength));
}

Try / catch

try {
  byob.respond(n);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('closed stream')) {
    return; // stream already closed: nothing left to deliver
  }
  throw e;
}

Prevention

When it happens

Trigger: controller.close() executes and then a completion callback (OS read, socket event, promise chain) still calls byobRequest.respond(n > 0); responding from a stale byobRequest captured in an earlier pull(); an error path that closes the stream and then falls through to the success path that responds.

Common situations: File/socket adapters where EOF-triggered close races an in-flight read completion; async write callbacks resolving after close; source code holding a request object across pulls.

Related errors


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