denoland/deno · error · TypeError
Request closed
Error message
Request closed
What it means
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.
Source
Thrown at ext/http/00_serve.ts:256
this.url();
this.headerList;
this.remoteAddr;
this.close();
this.#upgraded = true;
return op_http_upgrade_websocket_next(external);
}
}
url() {
if (this.#urlValue !== undefined) {
return this.#urlValue;
}
if (this.#external === null) {
throw new TypeError("Request closed");
}
if (this.#methodValue === undefined) {
this.#methodValue = op_http_get_request_method(this.#external);
}
return this.#urlValue = op_http_get_request_url(this.#external);
}
get completed() {
if (!this.#completed) {
// NOTE: this is faster than Promise.withResolvers()
let resolve, reject;
const promise = new Promise((r1, r2) => {
resolve = r1;
reject = r2;
});
this.#completed = { promise, resolve, reject };View on GitHub (pinned to 89f33cbef2)
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
Example fix
// before
async function handler(req, info) {
respond();
setTimeout(() => log(req.url), 1000); // TypeError: Request closed
}
// after
async function handler(req, info) {
const url = req.url; // capture eagerly
setTimeout(() => log(url), 1000);
} Defensive patterns
Strategy: try-catch
Validate before calling
// capture eagerly at handler entry; no runtime check can resurrect a closed request
async function handler(req, info) {
const meta = { url: req.url, method: req.method }; // safe while live
// ... later use meta.url, never req.url post-completion
} Try / catch
try {
log(req.url);
} catch (err) {
if (err instanceof TypeError && err.message === "Request closed") {
log("<closed>");
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Already closed
- Cannot read headers: request closed
- cannot read url: request closed
- Cannot read url: request closed
- Already upgraded
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/7aa21f38afda9410.
Report an issue: GitHub.