psf/requests · error · UnrewindableBodyError
An error occurred when rewinding request body for redirect.
Error message
An error occurred when rewinding request body for redirect.
What it means
Raised as `requests.exceptions.UnrewindableBodyError` from `rewind_body()` in `src/requests/utils.py:1151`. When a server redirects a request that had a file-like body, requests must seek the body back to its recorded start position (`_body_position`) to resend it; the body object has a `seek()` method, but calling it raised OSError. The stream is therefore in an unknown position and the redirect cannot be safely followed.
Source
Thrown at src/requests/utils.py:1151
netloc = netloc.rsplit("@", 1)[-1]
return urlunparse((scheme, netloc, path, params, query, ""))
def rewind_body(prepared_request: PreparedRequest) -> None:
"""Move file pointer back to its recorded starting position
so it can be read again on redirect.
"""
body_seek = getattr(prepared_request.body, "seek", None)
if body_seek is not None and isinstance(
prepared_request._body_position,
integer_types,
):
try:
body_seek(prepared_request._body_position)
except OSError:
raise UnrewindableBodyError(
"An error occurred when rewinding request body for redirect."
)
else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
View on GitHub ↗ (pinned to 414f0513c3)
Solutions
- Send the request to the final URL directly so no redirect occurs (fix http→https, add the trailing slash, use the region-correct endpoint).
- Buffer the body into memory or a real file first: `data=io.BytesIO(stream.read())`, which seeks reliably.
- Disable redirects and handle them yourself: `resp = requests.post(url, data=stream, allow_redirects=False)`, then re-open the stream for the new location.
- If seek fails because the file was closed, keep the file open for the duration of the request.
Example fix
# before
requests.post('http://api.example.com/upload', data=sys.stdin.buffer)
# after
import io
body = io.BytesIO(sys.stdin.buffer.read())
requests.post('https://api.example.com/upload', data=body) Defensive patterns
Strategy: try-catch
Validate before calling
# Ensure body is rewindable before sending: pass bytes/str, or a fresh seekable file body = open(path, 'rb') # seekable; avoid non-seekable streams (pipes, generators) with redirect-prone URLs
Try / catch
try:
resp = requests.post(url, data=stream)
except requests.exceptions.UnrewindableBodyError as e:
# seek() failed while replaying the body for a redirect
log.error('Body rewind failed on redirect: %s', e)
resp = requests.post(final_url, data=open(path, 'rb'), allow_redirects=False) Prevention
- Prefer in-memory bytes or seekable file objects over generators/pipes for request bodies
- When streaming uploads, resolve the final URL first (allow_redirects=False, follow Location manually) so no rewind is needed
- Reopen the source and retry against the redirect target if rewinding fails
When it happens
Trigger: An upload (`requests.post(url, data=fileobj)` or `files=`) whose target responds with a 301/302/307/308 redirect, where the body's `seek()` exists but fails — e.g. a pipe, socket, or `sys.stdin.buffer` wrapped so seek raises, an HTTP response object used as a body (`data=other_resp.raw`), or a file whose underlying handle was closed/consumed.
Common situations: Streaming one HTTP response directly into another request; uploading from stdin or a subprocess pipe; endpoints that redirect POST/PUT (e.g. http→https redirect, trailing-slash redirect, S3 region redirect) while the body is a non-seekable stream.
Related errors
- Unable to rewind request body for redirect.
- Streamed bodies and files are mutually exclusive.
- chunk_size must be an int, it is instead a {type(chunk_size)
- The content for this response was already consumed
- Exceeded {self.max_redirects} redirects.
AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31).
Data as JSON: /data/errors/a3498989f4bc6eba.json.
Report an issue: GitHub ↗.