aio-libs/aiohttp · error · ClientPayloadError
Cannot follow redirect with a consumed request body. Use…
Error message
Cannot follow redirect with a consumed request body. Use bytes, a seekable file-like object, or set allow_redirects=False.
What it means
Raised during redirect handling (status 307/308, or 301/302 with non-POST methods) when the original request body has already been consumed and cannot be replayed. The body must be re-sent verbatim on these redirects; aiohttp refuses to silently send an empty body.
Solutions
- Send bytes instead of a stream so the body can be replayed.
- Use a seekable file-like object and ensure aiohttp can rewind it (open(path, 'rb')).
- Set allow_redirects=False, inspect the 3xx response, and re-issue the request yourself with a fresh body.
- Buffer the stream into bytes up front when the body is small enough.
Example fix
// before
async def gen(): ...
await session.post(url, data=gen()) # 307 redirect -> raises
// after
body = b'...'
await session.post(url, data=body)
# or:
await session.post(url, data=open('file.bin','rb'), allow_redirects=True) Defensive patterns
Strategy: validation
Validate before calling
async def post_rewindable(session, url, body):
if isinstance(body, (bytes, bytearray)):
return await session.post(url, data=body)
if hasattr(body, 'seek'):
return await session.post(url, data=body)
# buffer streaming body
data = b''.join([c async for c in body]) if hasattr(body, '__aiter__') else bytes(body)
return await session.post(url, data=data) Type guard
def is_rewindable(body) -> bool:
return isinstance(body, (bytes, bytearray)) or hasattr(body, 'seek') Try / catch
from aiohttp import ClientPayloadError
try:
resp = await session.post(url, data=body)
except ClientPayloadError as e:
if 'consumed request body' in str(e):
resp = await session.post(url, data=body_bytes, allow_redirects=False)
else:
raise Prevention
- Send bytes for redirectable uploads, or pass an open file in binary mode.
- Set allow_redirects=False for one-shot streams and follow manually.
- Buffer small streams into bytes before sending.
When it happens
Trigger: POST/PUT of a non-rewindable stream (e.g. a streaming generator, a pipe, or a Payload backed by a consumed file) that receives a 307/308 redirect. Reading req._body.consumed returns True at redirect time.
Common situations: Uploading a large file from a stream that has been read once. Using async generators as request bodies against endpoints that redirect. Proxy/CGI backends that 307-redirect uploads.
Related errors
- Cannot call write() after write_eof()
- Cannot call write() before prepare()
- data and json parameters can not be used at the same time
- HTTP redirects need a location to redirect to.
- Invalid redirect URL origin
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/be3f472c6f872fb5.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/client.py:798
if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
resp.status in (301, 302) and resp.method == hdrs.METH_POST
):
method = hdrs.METH_GET
data = None
if headers.get(hdrs.CONTENT_LENGTH):
headers.pop(hdrs.CONTENT_LENGTH)
else:
# For 307/308, always preserve the request body
# For 301/302 with non-POST methods, preserve the request body
# https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
# Use the existing payload to avoid recreating it from
# a potentially consumed file.
#
# If the payload is already consumed and cannot be replayed,
# fail fast instead of silently sending an empty body.
if req._body.consumed:
resp.close()
raise ClientPayloadError(
"Cannot follow redirect with a consumed request "
"body. Use bytes, a seekable file-like object, "
"or set allow_redirects=False."
)
data = req._body
r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
hdrs.URI
)
if r_url is None:
# see github.com/aio-libs/aiohttp/issues/2022
break
else:
# reading from correct redirection
# response is forbidden
resp.release()
try:View on GitHub (pinned to d041d4d0fd)