{"id":"e35d8ce52f5ac8c3","repo":"psf/requests","slug":"unable-to-rewind-request-body-for-redirect","errorCode":null,"errorMessage":"Unable to rewind request body for redirect.","messagePattern":"Unable to rewind request body for redirect\\.","errorType":"exception","errorClass":"UnrewindableBodyError","httpStatus":null,"severity":"error","filePath":"src/requests/utils.py","lineNumber":1155,"sourceCode":"\n\ndef rewind_body(prepared_request: PreparedRequest) -> None:\n    \"\"\"Move file pointer back to its recorded starting position\n    so it can be read again on redirect.\n    \"\"\"\n    body_seek = getattr(prepared_request.body, \"seek\", None)\n    if body_seek is not None and isinstance(\n        prepared_request._body_position,\n        integer_types,\n    ):\n        try:\n            body_seek(prepared_request._body_position)\n        except OSError:\n            raise UnrewindableBodyError(\n                \"An error occurred when rewinding request body for redirect.\"\n            )\n    else:\n        raise UnrewindableBodyError(\"Unable to rewind request body for redirect.\")\n","sourceCodeStart":1137,"sourceCodeEnd":1156,"githubUrl":"https://github.com/psf/requests/blob/414f0513c33883adf6f2b46901d4f0b38a455851/src/requests/utils.py#L1137-L1156","documentation":"Raised as `requests.exceptions.UnrewindableBodyError` from `rewind_body()` in `src/requests/utils.py:1155`. This is the branch where the request body either has no `seek()` method at all or requests never recorded a valid integer `_body_position` (it records `IncompleteRead`/failure sentinels when the initial `tell()` failed). Without both, the already-consumed streamed body cannot be replayed on a redirect, so requests refuses to follow it with a corrupt/empty body.","triggerScenarios":"Redirected POST/PUT where the body is a generator (`data=my_generator()`), an iterator for chunked uploads, or any object without `seek`/`tell`; or a file-like object whose `tell()` raised during `prepare_body`, leaving `_body_position` as a non-integer sentinel.","commonSituations":"Chunked uploads using generators to APIs that issue http→https or trailing-slash redirects; load-balancer or auth-gateway redirects in front of upload endpoints; wrapping compression/encryption generators around file uploads.","solutions":["Point the request at the final, canonical URL (https, correct host, trailing slash) so no redirect happens — this is the usual fix.","Replace the generator/iterator body with a seekable object: read it into `io.BytesIO` or a temp file before sending.","Set `allow_redirects=False`, inspect `resp.headers['Location']`, recreate the generator, and resend manually.","For 307/308-heavy APIs, check whether the API client library offers a native retry/redirect mechanism that re-creates the body."],"exampleFix":"# before\ndef gen():\n    for chunk in produce_chunks():\n        yield chunk\nrequests.post('http://api.example.com/upload', data=gen())\n# after\nimport io\nbuf = io.BytesIO(b''.join(produce_chunks()))\nrequests.post('https://api.example.com/upload', data=buf)","handlingStrategy":"try-catch","validationCode":"def body_is_rewindable(body):\n    return isinstance(body, (bytes, str)) or (hasattr(body, 'seek') and hasattr(body, 'tell'))","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(url, data=body_stream)\nexcept requests.exceptions.UnrewindableBodyError:\n    # body has no usable seek/tell — cannot replay for the redirect\n    resp = requests.post(url, data=body_stream_factory(), allow_redirects=False)","preventionTips":["Check hasattr(body, 'seek') before sending stream bodies to endpoints that may redirect","Use a body factory (re-create the stream per attempt) instead of a one-shot generator","Disable redirects for streaming uploads and handle 3xx explicitly"],"tags":["redirect","upload","streaming","chunked","python","requests"],"analyzedSha":"414f0513c33883adf6f2b46901d4f0b38a455851","analyzedAt":"2026-07-31T19:24:57.696Z","schemaVersion":2}