denoland/deno · error · TypeError
Request closed
Error message
Request closed
What it means
The internal serve request object caches its URL lazily from the native handle. Once the request is finalized and the native handle released (#external === null), calling url() throws a TypeError ('Request closed'). Practically: reading the request URL after the response has completed touches a dead resource.
Solutions
- Read request.url (and any other request data) synchronously at handler entry and capture it into a local
- Pass plain copied values (url string, method, headers snapshot) to async/background work instead of the Request object
- Move post-response logging into code that already holds the captured values, not the request
Example fix
// before
Deno.serve((req) => {
queueMicrotask(() => log(req.url)); // may run after close -> TypeError
return new Response("ok");
});
// after
Deno.serve((req) => {
const url = req.url; // capture synchronously
queueMicrotask(() => log(url));
return new Response("ok");
}); Defensive patterns
Strategy: validation
Validate before calling
Deno.serve((req) => {
const ctx = {
url: req.url, // capture synchronously
method: req.method,
headers: [...req.headers], // snapshot
};
scheduleBackgroundLog(ctx); // safe: only uses the snapshot
return handle(ctx, req);
}); Try / catch
try {
log(req.url);
} catch (err) {
if (err instanceof TypeError && err.message === "Request closed") {
// too late: fall back to a previously captured value
} else throw err;
} Prevention
- Capture request.url/method/headers into locals at handler entry
- Pass plain copied data to background tasks, never the Request object
- Finish request-dependent logging before returning the response
When it happens
Trigger: Accessing the request's URL in a detached task (fire-and-forget promise) after the handler returned; logging or metrics code that reads request.url after await respondWith(...); accessing a stored request object in a queue processed after the response finished.
Common situations: Access logs written in background flushers; request objects captured in closures and inspected later; spans/traces closed asynchronously after the response.
Related errors
- Already closed
- Deno.serve: request.signal aborts on successful responses…
- Already upgraded
- Cannot read headers: request closed
- cannot read url: request closed
AI-assisted analysis of denoland/deno@abd22074e4 (2026-08-20).
Data as JSON: /api/errors/7aa21f38afda9410.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/00_serve.ts:271
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 abd22074e4)