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

  1. Send the request to the final URL directly so no redirect occurs (fix http→https, add the trailing slash, use the region-correct endpoint).
  2. Buffer the body into memory or a real file first: `data=io.BytesIO(stream.read())`, which seeks reliably.
  3. Disable redirects and handle them yourself: `resp = requests.post(url, data=stream, allow_redirects=False)`, then re-open the stream for the new location.
  4. 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

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


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/a3498989f4bc6eba.json. Report an issue: GitHub ↗.