{"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/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_request.py#L191-L227","documentation":"Raised as RuntimeError by BaseRequest.clone (aiohttp/web_request.py:209) when self._read_bytes is already truthy - i.e. the request body has been read via .read() / .text() / .json() / .post(). clone() shares the same underlying payload (self._payload) by reference, so once bytes have been consumed the clone would receive an empty or inconsistent body. aiohttp guards against this silently corrupting downstream consumers.","triggerScenarios":"Calling request.clone(...)' after request.read() (or .text()/.json()/.post()) has populated _read_bytes. Common in middleware that reads the body for logging/validation and then attempts to clone the request to pass a modified copy downstream.","commonSituations":"Custom middleware that needs to rewrite headers (method, path) after the body has been inspected; auth handlers that decode JSON to inspect claims and then forward the request; debugging code that logs request.text() before the main handler.","solutions":["Call clone() before reading the body, then read from the clone (or pass the cached bytes through request['body'] in the middleware).","Cache the read body in request['cached_body'] and have the downstream handler use it instead of re-reading.","Restructure middleware to only inspect headers / transport-level info, leaving body reads to the final handler."],"exampleFix":"// before\nasync def mw(handler, request):\n    body = await request.read()      # populates _read_bytes\n    log_body(body)\n    new_req = request.clone(method=\"POST\")  # RuntimeError\n    return await handler(new_req)\n\n# after\nasync def mw(handler, request):\n    new_req = request.clone(method=\"POST\")  # clone first\n    body = await new_req.read()\n    log_body(body)\n    return await handler(new_req)","handlingStrategy":"validation","validationCode":"if request._read_bytes is None:\n    cloned = request.clone(method=\"POST\")\nelse:\n    raise RuntimeError(\"clone before reading body\")","typeGuard":"def can_clone(request) -> bool:\n    return request._read_bytes is None","tryCatchPattern":"try:\n    new_req = request.clone(method=\"POST\")\nexcept RuntimeError:\n    new_req = request  # or rebuild from cached body in request['cached_body']","preventionTips":["Always clone() before any read of the body in middleware.","Cache read bodies in request['cached_body'] for downstream handlers.","Restructure middleware so body reads happen once, in the final handler."],"tags":["request","middleware","body","clone"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}