{"record":{"id":"7aa21f38afda9410","repo":"denoland/deno","slug":"request-closed","errorCode":null,"errorMessage":"Request closed","messagePattern":"Request closed","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/00_serve.ts","lineNumber":256,"sourceCode":"\n      this.url();\n      this.headerList;\n      this.remoteAddr;\n      this.close();\n\n      this.#upgraded = true;\n\n      return op_http_upgrade_websocket_next(external);\n    }\n  }\n\n  url() {\n    if (this.#urlValue !== undefined) {\n      return this.#urlValue;\n    }\n\n    if (this.#external === null) {\n      throw new TypeError(\"Request closed\");\n    }\n\n    if (this.#methodValue === undefined) {\n      this.#methodValue = op_http_get_request_method(this.#external);\n    }\n\n    return this.#urlValue = op_http_get_request_url(this.#external);\n  }\n\n  get completed() {\n    if (!this.#completed) {\n      // NOTE: this is faster than Promise.withResolvers()\n      let resolve, reject;\n      const promise = new Promise((r1, r2) => {\n        resolve = r1;\n        reject = r2;\n      });\n      this.#completed = { promise, resolve, reject };","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/00_serve.ts#L238-L274","documentation":"InnerRequest.url() reads the request URL from the native handle; it caches the value on first access but the very first call must reach the handle. Once the request is closed (response delivered or connection finished) the handle is null and the URL is no longer queryable, so a TypeError 'Request closed' is thrown. It is a TypeError rather than Deno.errors.Http because url() is also used from non-upgrade paths.","triggerScenarios":"Accessing the request's URL lazily after the handler has returned and the response completed — e.g. in a promise callback, log flush, or error handler that captures the request object and touches url() post-completion.","commonSituations":"Deferred access logging that batches and reads request.url after the response is sent; background tasks capturing the request object; error reporting inside catch blocks that run after close; post-response analytics.","solutions":["Capture request.url (and anything else needed) into a local const at the top of the handler","Pass plain values (method, url, headers) into background work instead of the Request object","Wrap post-completion access in try/catch on TypeError when you cannot restructure"],"exampleFix":"// before\nasync function handler(req, info) {\n  respond();\n  setTimeout(() => log(req.url), 1000); // TypeError: Request closed\n}\n\n// after\nasync function handler(req, info) {\n  const url = req.url; // capture eagerly\n  setTimeout(() => log(url), 1000);\n}","handlingStrategy":"try-catch","validationCode":"// capture eagerly at handler entry; no runtime check can resurrect a closed request\nasync function handler(req, info) {\n  const meta = { url: req.url, method: req.method }; // safe while live\n  // ... later use meta.url, never req.url post-completion\n}","typeGuard":null,"tryCatchPattern":"try {\n  log(req.url);\n} catch (err) {\n  if (err instanceof TypeError && err.message === \"Request closed\") {\n    log(\"<closed>\");\n  } else throw err;\n}","preventionTips":["Copy url/method/headers into plain values at the top of the handler","Pass captured values, not Request objects, into background tasks","Do deferred logging from the values you snapshotted, not from the live request"],"tags":["http","serve","lifecycle","request"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}