aio-libs/aiohttp · error · RuntimeError
Cannot clone request after reading its content
Error message
Cannot clone request after reading its content
What it means
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.
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.
Example fix
# before body = await request.read() clone = request.clone(method='PUT') # after clone = request.clone(method='PUT') body = await clone.read()
Defensive patterns
Strategy: validation
Validate before calling
if request._read_bytes:
raise RuntimeError('already read')
clone = request.clone() Type guard
def can_clone(req: BaseRequest) -> bool:
return not req._read_bytes Prevention
- Clone before reading in middleware.
- Read from the clone, not the original request.
- Check request.can_read_body to reason about state.
When it happens
Trigger: Awaiting request.read() / request.text() / request.json() / request.post() and then calling request.clone().
Common situations: Middleware that reads the body for logging or signature verification, then forwards a cloned request; body inspection followed by a downstream clone.
Related errors
- body and text are not allowed together
- chunked can not be set if Content-Length header is set
- chunked can not be set if "Transfer-Encoding: chunked"…
- compress can not be set if Content-Encoding header is set
- compress must be one of True, False, 'deflate', or 'gzip'
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/dd7add817a3ceb36.
Report an issue: GitHub.
Appendix: 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 d041d4d0fd)