psf/requests · error · TooManyRedirects

Exceeded {self.max_redirects} redirects.

Error message

Exceeded {self.max_redirects} redirects.

What it means

Raised as `requests.exceptions.TooManyRedirects` from `Session.resolve_redirects()` in `src/requests/sessions.py:217` when the accumulated redirect history reaches `self.max_redirects` (default 30, from `DEFAULT_REDIRECT_LIMIT`). It is a loop guard: each hop is appended to `resp.history`, and once the limit is hit requests stops following and raises, attaching the last response as `e.response`.

Source

Thrown at src/requests/sessions.py:217

        hist: list[Response] = []  # keep track of history

        url = self.get_redirect_target(resp)
        previous_fragment = urlparse(req.url).fragment
        while url:
            prepared_request = req.copy()

            # Update history and keep track of redirects.
            resp.history = hist[:]
            hist.append(resp)

            try:
                resp.content  # Consume socket so it can be released
            except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
                resp.raw.read(decode_content=False)

            if len(resp.history) >= self.max_redirects:
                raise TooManyRedirects(
                    f"Exceeded {self.max_redirects} redirects.", response=resp
                )

            # Release the connection back into the pool.
            resp.close()

            # Handle redirection without scheme (see: RFC 1808 Section 4)
            if url.startswith("//"):
                parsed_rurl = urlparse(resp.url)
                url = ":".join([to_native_string(parsed_rurl.scheme), url])

            # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
            parsed = urlparse(url)
            if parsed.fragment == "" and previous_fragment:
                parsed = parsed._replace(fragment=previous_fragment)
            elif parsed.fragment:
                previous_fragment = parsed.fragment
            url = parsed.geturl()

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Inspect the loop: catch the exception and print `e.response.history` / `e.response.headers['Location']` to see where it bounces, or request with `allow_redirects=False` to see the first hop.
  2. Use a `requests.Session()` so cookies set during the redirect chain persist — auth/consent loops are the top cause.
  3. Fix the request so the server stops redirecting: correct scheme/host, valid credentials or token, expected User-Agent header.
  4. Only if the chain is legitimately long, raise the limit: `session.max_redirects = 60`.

Example fix

# before
r = requests.get('http://example.com/dashboard')  # bounces to /login forever (cookies not kept)
# after
s = requests.Session()
s.post('https://example.com/login', data=creds)  # session keeps auth cookie
r = s.get('https://example.com/dashboard')
Defensive patterns

Strategy: try-catch

Validate before calling

# Bound or raise the limit deliberately per session
s = requests.Session()
s.max_redirects = 5

Try / catch

try:
    resp = session.get(url)
except requests.exceptions.TooManyRedirects as e:
    # likely a redirect loop; inspect the partial history
    log.error('Redirect loop for %s: %s', url, [r.url for r in e.response.history] if e.response is not None else e)

Prevention

When it happens

Trigger: Any call with `allow_redirects=True` (the default for GET) where the server chain exceeds `max_redirects` hops — most often a redirect loop (A→B→A) or a login wall that redirects every request back to itself. Also triggered quickly if the user lowered `session.max_redirects`.

Common situations: Missing/expired auth cookies causing an endless bounce to a login page; http↔https or www↔apex rewrite loops in misconfigured nginx/CDN rules; sites that redirect based on a cookie or User-Agent the client never persists (using bare `requests.get` instead of a `Session` so cookies are dropped between hops); URL-normalization loops (trailing slash added and removed).

Related errors


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