{"record":{"id":"8318bcaccafdffbd","repo":"denoland/deno","slug":"cannot-read-headers-request-closed","errorCode":null,"errorMessage":"Cannot read headers: request closed","messagePattern":"Cannot read headers: request closed","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/fetch/23_request.js","lineNumber":146,"sourceCode":"    StringPrototypeStartsWith(url, \"blob:\")\n  ) {\n    blobUrlEntry = blobFromObjectUrl(url);\n  }\n  return {\n    methodInner: method,\n    get method() {\n      return this.methodInner;\n    },\n    set method(value) {\n      this.methodInner = value;\n    },\n    headerListInner: null,\n    get headerList() {\n      if (this.headerListInner === null) {\n        try {\n          this.headerListInner = headerList();\n        } catch {\n          throw new TypeError(\"Cannot read headers: request closed\");\n        }\n      }\n      return this.headerListInner;\n    },\n    set headerList(value) {\n      this.headerListInner = value;\n    },\n    body,\n    redirectMode: \"follow\",\n    redirectCount: 0,\n    urlList: [typeof url === \"string\" ? () => url : url],\n    urlListProcessed: [],\n    clientRid: null,\n    blobUrlEntry,\n    url() {\n      if (this.urlListProcessed[0] === undefined) {\n        try {\n          this.urlListProcessed[0] = this.urlList[0]();","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/fetch/23_request.js#L128-L164","documentation":"In Deno's fetch implementation an op-backed InnerRequest (e.g. a request object handed to a Deno.serve / Deno.serveHttp handler) materializes its header list lazily: the first access to the headers calls into the native request resource. If that resource has already been closed — the handler returned, the connection finished, the request was dropped — the native read throws and is re-thrown as TypeError \"Cannot read headers: request closed\".","triggerScenarios":"Accessing request.headers on a server-side Request inside a setTimeout/queueMicrotask that runs after the handler returned; storing req objects in a queue and reading headers later; logging headers after an await that outlives the response.","commonSituations":"Background-job patterns that stash the incoming request for async processing; fire-and-forget telemetry after responding; retry queues holding the original request; tests that keep the request object alive past the response.","solutions":["Read the headers you need synchronously at the top of the handler, before any await","Snapshot the request data ([...req.headers], url, method) into a plain object and pass that along","Consume/copy the body into memory (await req.text()) if the background task also needs the body","If you see this from a stored request, treat it as a lifecycle bug: capture data earlier rather than catching the error"],"exampleFix":"// before\nDeno.serve((req) => {\n  setTimeout(() => console.log(req.headers.get(\"x-id\")), 10); // TypeError: request closed\n  return new Response(\"ok\");\n});\n\n// after\nDeno.serve((req) => {\n  const id = req.headers.get(\"x-id\"); // read while the request is alive\n  setTimeout(() => console.log(id), 10);\n  return new Response(\"ok\");\n});","handlingStrategy":"try-catch","validationCode":"// No pre-check API exists — capture instead:\nfunction snapshotRequest(req) {\n  return { method: req.method, url: req.url, headers: [...req.headers] }; // safe while alive\n}","typeGuard":"function requestIsAlive(req) {\n  try {\n    req.url; // cheap lazy read that throws once the resource is closed\n    return true;\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  const auth = req.headers.get(\"authorization\");\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes(\"request closed\")) {\n    // the request lifecycle ended; use previously captured data\n  } else throw err;\n}","preventionTips":["Read headers at the top of the handler, before the first await","Never store the live request object for post-response work — store a plain snapshot","Treat 'request closed' TypeErrors as lifecycle bugs, not transient failures to retry"],"tags":["fetch","request","headers","resource-lifecycle","serve"],"backgroundTag":"use-after-close","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}