denoland/deno · error · TypeError

Cannot read url: request closed

Error message

Cannot read url: request closed

What it means

InnerRequest.currentUrl() lazily resolves the most recent URL in the request's URL list (used during redirect following) by calling the stored urlList callback. For op-backed requests that callback is a native read; if the request resource has been closed by the time currentUrl() runs, the native call throws and is re-thrown as TypeError "Cannot read url: request closed".

Solutions

  1. Forward a fresh Request built from snapshotted data instead of reusing the server request object
  2. Perform redirect-following fetches while the original request is still alive (start them in the handler)
  3. Capture req.url up front and construct a new Request(url, { method, headers, body }) for deferred work

Example fix

// before
Deno.serve((req) => {
  jobs.push(() => fetch(req)); // later: TypeError: Cannot read url: request closed
  return new Response("accepted");
});

// after
Deno.serve((req) => {
  const snapshot = { url: req.url, method: req.method, headers: [...req.headers] };
  jobs.push(() => fetch(snapshot.url, { method: snapshot.method, headers: snapshot.headers }));
  return new Response("accepted");
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Capture before the handler returns, then re-fetch with explicit data:
function forwardLater(req) {
  const snapshot = { url: req.url, method: req.method, headers: [...req.headers] };
  return () => fetch(snapshot.url, { method: snapshot.method, headers: snapshot.headers });
}

Try / catch

try {
  await fetch(req); // may hit currentUrl() on redirects if the source closed
} catch (err) {
  if (err instanceof TypeError && err.message.includes("request closed")) {
    // rebuild from snapshot and retry once
  } else throw err;
}

Prevention

When it happens

Trigger: Redirect handling (fetch internals calling currentUrl on redirect) against an op-backed request that got closed mid-flight; user code or libraries that walk a server request's url list after the connection ended.

Common situations: Calling fetch() with an already-consumed server-side Request; proxies that forward the incoming request object asynchronously after responding; tests that reuse a request across ticks after the server released it.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/c12435333278e771. Report an issue: GitHub.

Appendix: source

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

    clientRid: null,
    blobUrlEntry,
    url() {
      if (this.urlListProcessed[0] === undefined) {
        try {
          this.urlListProcessed[0] = this.urlList[0]();
        } catch {
          throw new TypeError("cannot read url: request closed");
        }
      }
      return this.urlListProcessed[0];
    },
    currentUrl() {
      const currentIndex = this.urlList.length - 1;
      if (this.urlListProcessed[currentIndex] === undefined) {
        try {
          this.urlListProcessed[currentIndex] = this.urlList[currentIndex]();
        } catch {
          throw new TypeError("Cannot read url: request closed");
        }
      }
      return this.urlListProcessed[currentIndex];
    },
  };
}

/**
 * https://fetch.spec.whatwg.org/#concept-request-clone
 * @param {InnerRequest} request
 * @param {boolean} skipBody
 * @returns {InnerRequest}
 */
function cloneInnerRequest(request, skipBody = false) {
  const headerList = ArrayPrototypeMap(
    request.headerList,
    (x) => [x[0], x[1]],
  );

View on GitHub (pinned to 9ad36f7a2c)