{"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":790,"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":772,"sourceCodeEnd":808,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client.py#L772-L808","documentation":"Raised in the redirect branch of `_request` (client.py:788-794) as a `ClientPayloadError`. For 307/308 redirects (and 301/302 on non-POST methods) the request body must be replayed verbatim, but if the body payload has already been consumed (streamed out) it cannot be rewound. Rather than silently sending an empty body and corrupting the request, aiohttp fails fast. The fix is to give it a replayable body.","triggerScenarios":"POSTing/PUTing a non-seekable stream or an async generator as `data=` to a URL that returns 307/308 (which by spec must replay the body). Also when 301/302 is returned for a non-POST method (PUT/DELETE) — those preserve the body too.","commonSituations":"Uploading a file via a pipe or network stream that can't seek; passing an `aiohttp.streamer` async generator; servers that redirect POSTs with 307 instead of the more common 302 (which converts to GET and drops the body).","solutions":["Pass the body as `bytes` / `bytearray` so it can be replayed.","Use a seekable file-like object and rewind before the redirect (`f.seek(0)`); aiohttp will seek on replay.","Disable redirect following for that call: `allow_redirects=False`, then inspect the Location and re-issue manually.","For one-shot streams, read the whole body into memory first (`data=await stream.read()`)."],"exampleFix":"// before\nawait session.post(url, data=some_pipe_stream)  # server returns 307\n// after\nbody = b'...'  # or await f.read() into bytes\nawait session.post(url, data=body)\n// or\nawait session.post(url, data=seekable_file, allow_redirects=False)","handlingStrategy":"validation","validationCode":"def replayable_body(body) -> bool:\n    return isinstance(body, (bytes, bytearray, str)) or (\n        hasattr(body, 'read') and hasattr(body, 'seek')\n    )","typeGuard":"def is_replayable_body(body) -> bool:\n    return isinstance(body, (bytes, bytearray)) or (\n        hasattr(body, 'seek') and hasattr(body, 'read')\n    )","tryCatchPattern":null,"preventionTips":["For methods that may 307/308 (POST/PUT/DELETE), pass bytes or seekable files.","Set allow_redirects=False for one-shot streams and re-issue manually.","Read streams fully into memory before posting if the body is small.","Watch for `consumed` state on async-generator payloads."],"tags":["client","redirect","request-body","streaming"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}