{"record":{"id":"dd7add817a3ceb36","repo":"aio-libs/aiohttp","slug":"cannot-clone-request-after-reading-its-content","errorCode":null,"errorMessage":"Cannot clone request after reading its content","messagePattern":"Cannot clone request after reading its content","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_request.py","lineNumber":209,"sourceCode":"    def clone(\n        self,\n        *,\n        method: str | _SENTINEL = sentinel,\n        rel_url: StrOrURL | _SENTINEL = sentinel,\n        headers: LooseHeaders | _SENTINEL = sentinel,\n        scheme: str | _SENTINEL = sentinel,\n        host: str | _SENTINEL = sentinel,\n        remote: str | _SENTINEL = sentinel,\n        client_max_size: int | _SENTINEL = sentinel,\n    ) -> \"BaseRequest\":\n        \"\"\"Clone itself with replacement some attributes.\n\n        Creates and returns a new instance of Request object. If no parameters\n        are given, an exact copy is returned. If a parameter is not passed, it\n        will reuse the one from the current request object.\n        \"\"\"\n        if self._read_bytes:\n            raise RuntimeError(\"Cannot clone request after reading its content\")\n\n        dct: dict[str, Any] = {}\n        if method is not sentinel:\n            dct[\"method\"] = method\n        if rel_url is not sentinel:\n            new_url: URL = URL(rel_url)\n            dct[\"url\"] = new_url\n            dct[\"path\"] = str(new_url)\n        if headers is not sentinel:\n            # a copy semantic\n            new_headers = HeadersDictProxy(CIMultiDict(headers))\n            dct[\"headers\"] = new_headers\n            dct[\"raw_headers\"] = tuple(\n                (k.encode(\"utf-8\"), v.encode(\"utf-8\"))\n                for k, v in new_headers._md.items()\n            )\n\n        message = self._message._replace(**dct)","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_request.py#L191-L227","documentation":"BaseRequest.clone() refuses to run after the body has been read (self._read_bytes is truthy). A clone copies the request metadata, but a half-consumed body stream cannot be coherently duplicated, so aiohttp guards the operation up front.","triggerScenarios":"Awaiting request.read() / request.text() / request.json() / request.post() and then calling request.clone().","commonSituations":"Middleware that reads the body for logging or signature verification, then forwards a cloned request; body inspection followed by a downstream clone.","solutions":["Call request.clone() BEFORE any body-reading call.","In middleware, read from the clone, not the original request.","Check request.can_read_body / request._read_bytes before deciding to read."],"exampleFix":"# before\nbody = await request.read()\nclone = request.clone(method='PUT')\n# after\nclone = request.clone(method='PUT')\nbody = await clone.read()","handlingStrategy":"validation","validationCode":"if request._read_bytes:\n    raise RuntimeError('already read')\nclone = request.clone()","typeGuard":"def can_clone(req: BaseRequest) -> bool:\n    return not req._read_bytes","tryCatchPattern":null,"preventionTips":["Clone before reading in middleware.","Read from the clone, not the original request.","Check request.can_read_body to reason about state."],"tags":["request","clone","body","middleware"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}