denoland/deno · error · TypeError

Cannot read headers: request closed

Error message

Cannot read headers: request closed

What it means

InnerRequest's headerList getter materializes headers lazily by calling the headerList() function captured at request creation. When that underlying source (an op-backed request, e.g. one deserialized from a server connection or transfer stream) has already been closed, the call throws and the getter converts it into TypeError 'Cannot read headers: request closed'. It means the request object outlived the resource that owns its header data.

Source

Thrown at ext/fetch/23_request.js:146

    StringPrototypeStartsWith(url, "blob:")
  ) {
    blobUrlEntry = blobFromObjectUrl(url);
  }
  return {
    methodInner: method,
    get method() {
      return this.methodInner;
    },
    set method(value) {
      this.methodInner = value;
    },
    headerListInner: null,
    get headerList() {
      if (this.headerListInner === null) {
        try {
          this.headerListInner = headerList();
        } catch {
          throw new TypeError("Cannot read headers: request closed");
        }
      }
      return this.headerListInner;
    },
    set headerList(value) {
      this.headerListInner = value;
    },
    body,
    redirectMode: "follow",
    redirectCount: 0,
    urlList: [typeof url === "string" ? () => url : url],
    urlListProcessed: [],
    clientRid: null,
    blobUrlEntry,
    url() {
      if (this.urlListProcessed[0] === undefined) {
        try {
          this.urlListProcessed[0] = this.urlList[0]();

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Copy out the headers (e.g. Object.fromEntries(request.headers)) synchronously when the request first arrives, before any await
  2. Stop touching the Request after the connection/transfer that owns it closes; pass plain data to deferred work instead
  3. Abort or finish in-flight request processing when the underlying connection's close/abort signal fires

Example fix

// before
const pending = [];
Deno.serve((req) => {
  pending.push(req); // kept past connection lifetime
  return new Response('ok');
});
// later, connection already closed:
for (const r of pending) console.log(r.headers.get('x-id')); // throws

// after
const pending = [];
Deno.serve((req) => {
  pending.push(Object.fromEntries(req.headers)); // snapshot now
  return new Response('ok');
});
Defensive patterns

Strategy: try-catch

Validate before calling

function snapshotHeaders(req) {
  try {
    return Object.fromEntries(req.headers);
  } catch {
    return null; // request already closed
  }
}

Try / catch

try {
  headers = Object.fromEntries(req.headers);
} catch (err) {
  if (err instanceof TypeError && err.message === 'Cannot read headers: request closed') {
    // connection dropped; skip or use last-known snapshot
  } else throw err;
}

Prevention

When it happens

Trigger: Reading request.headers (directly or via fetch internals) on an InnerRequest whose op resource was closed beforehand — e.g. a server-side or worker-transferred Request accessed after the connection/MessagePort that produced it closed.

Common situations: Stashing an incoming Request for later processing after the HTTP connection dropped, or after response completion when the runtime reaped the connection resource; race between an aborted client and handler code still inspecting headers.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/8318bcaccafdffbd. Report an issue: GitHub.