aio-libs/aiohttp · error · RuntimeError
Cannot clone request after reading its content
Error message
Cannot clone request after reading its content
What it means
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.
Source
Thrown at aiohttp/web_request.py:209
def clone(
self,
*,
method: str | _SENTINEL = sentinel,
rel_url: StrOrURL | _SENTINEL = sentinel,
headers: LooseHeaders | _SENTINEL = sentinel,
scheme: str | _SENTINEL = sentinel,
host: str | _SENTINEL = sentinel,
remote: str | _SENTINEL = sentinel,
client_max_size: int | _SENTINEL = sentinel,
) -> "BaseRequest":
"""Clone itself with replacement some attributes.
Creates and returns a new instance of Request object. If no parameters
are given, an exact copy is returned. If a parameter is not passed, it
will reuse the one from the current request object.
"""
if self._read_bytes:
raise RuntimeError("Cannot clone request after reading its content")
dct: dict[str, Any] = {}
if method is not sentinel:
dct["method"] = method
if rel_url is not sentinel:
new_url: URL = URL(rel_url)
dct["url"] = new_url
dct["path"] = str(new_url)
if headers is not sentinel:
# a copy semantic
new_headers = HeadersDictProxy(CIMultiDict(headers))
dct["headers"] = new_headers
dct["raw_headers"] = tuple(
(k.encode("utf-8"), v.encode("utf-8"))
for k, v in new_headers._md.items()
)
message = self._message._replace(**dct)View on GitHub (pinned to c0ef574e29)
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.
Example fix
// before
async def mw(handler, request):
body = await request.read() # populates _read_bytes
log_body(body)
new_req = request.clone(method="POST") # RuntimeError
return await handler(new_req)
# after
async def mw(handler, request):
new_req = request.clone(method="POST") # clone first
body = await new_req.read()
log_body(body)
return await handler(new_req) Defensive patterns
Strategy: validation
Validate before calling
if request._read_bytes is None:
cloned = request.clone(method="POST")
else:
raise RuntimeError("clone before reading body") Type guard
def can_clone(request) -> bool:
return request._read_bytes is None Try / catch
try:
new_req = request.clone(method="POST")
except RuntimeError:
new_req = request # or rebuild from cached body in request['cached_body'] Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- {key}
- Duplicate '{name}' header found.
- Bad line ending, expected CRLF
- Missing 'Host' header in request.
- Request has duplicate `chunked` Transfer-Encoding
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/dd7add817a3ceb36.json.
Report an issue: GitHub.