{"record":{"id":"be3f472c6f872fb5","repo":"aio-libs/aiohttp","slug":"cannot-follow-redirect-with-a-consumed-request-bod","errorCode":null,"errorMessage":"Cannot follow redirect with a consumed request body. Use bytes, a seekable file-like object, or set allow_redirects=False.","messagePattern":"Cannot follow redirect with a consumed request body\\. Use bytes, a seekable file-like object, or set allow_redirects=False\\.","errorType":"exception","errorClass":"ClientPayloadError","httpStatus":null,"severity":"error","filePath":"aiohttp/client.py","lineNumber":798,"sourceCode":"                        if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (\n                            resp.status in (301, 302) and resp.method == hdrs.METH_POST\n                        ):\n                            method = hdrs.METH_GET\n                            data = None\n                            if headers.get(hdrs.CONTENT_LENGTH):\n                                headers.pop(hdrs.CONTENT_LENGTH)\n                        else:\n                            # For 307/308, always preserve the request body\n                            # For 301/302 with non-POST methods, preserve the request body\n                            # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1\n                            # Use the existing payload to avoid recreating it from\n                            # a potentially consumed file.\n                            #\n                            # If the payload is already consumed and cannot be replayed,\n                            # fail fast instead of silently sending an empty body.\n                            if req._body.consumed:\n                                resp.close()\n                                raise ClientPayloadError(\n                                    \"Cannot follow redirect with a consumed request \"\n                                    \"body. Use bytes, a seekable file-like object, \"\n                                    \"or set allow_redirects=False.\"\n                                )\n                            data = req._body\n\n                        r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(\n                            hdrs.URI\n                        )\n                        if r_url is None:\n                            # see github.com/aio-libs/aiohttp/issues/2022\n                            break\n                        else:\n                            # reading from correct redirection\n                            # response is forbidden\n                            resp.release()\n\n                        try:","sourceCodeStart":780,"sourceCodeEnd":816,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/client.py#L780-L816","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nasync def gen(): ...\nawait session.post(url, data=gen())  # 307 redirect -> raises\n// after\nbody = b'...'\nawait session.post(url, data=body)\n# or:\nawait session.post(url, data=open('file.bin','rb'), allow_redirects=True)","handlingStrategy":"validation","validationCode":"async def post_rewindable(session, url, body):\n    if isinstance(body, (bytes, bytearray)):\n        return await session.post(url, data=body)\n    if hasattr(body, 'seek'):\n        return await session.post(url, data=body)\n    # buffer streaming body\n    data = b''.join([c async for c in body]) if hasattr(body, '__aiter__') else bytes(body)\n    return await session.post(url, data=data)","typeGuard":"def is_rewindable(body) -> bool:\n    return isinstance(body, (bytes, bytearray)) or hasattr(body, 'seek')","tryCatchPattern":"from aiohttp import ClientPayloadError\n\ntry:\n    resp = await session.post(url, data=body)\nexcept ClientPayloadError as e:\n    if 'consumed request body' in str(e):\n        resp = await session.post(url, data=body_bytes, allow_redirects=False)\n    else:\n        raise","preventionTips":["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."],"tags":["redirect","request-body","streaming"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}