psf/requests · error · TooManyRedirects

Exceeded redirects.

Error message

Exceeded {self.max_redirects} redirects.

What it means

Raised by Session.resolve_redirects() when the number of HTTP redirects in a chain reaches or exceeds session.max_redirects (default 30). This is a safety mechanism to prevent infinite redirect loops. The response object is attached so the caller can inspect the last redirect response.

Solutions

  1. Investigate the redirect chain with allow_redirects=False to inspect each Location header manually.
  2. If the redirect count is legitimately high, increase session.max_redirects to a safe ceiling.
  3. Fix the server-side redirect loop — exceeding 30 redirects almost always indicates a bug.

Example fix

# before
resp = session.get(url)  # TooManyRedirects

# after — diagnose the chain
resp = session.get(url, allow_redirects=False)
print(resp.status_code, resp.headers.get('Location'))

# or increase the limit if legitimate
session.max_redirects = 50
Defensive patterns

Strategy: try-catch

Validate before calling

session.max_redirects = 10  # set a sensible limit for your use case

Try / catch

try:
    resp = session.get(url)
except requests.TooManyRedirects as e:
    logger.error(f'Redirect chain exceeded limit. Last URL: {e.response.url}')
    # Debug with allow_redirects=False
    resp = session.get(url, allow_redirects=False)

Prevention

When it happens

Trigger: A URL that redirects more than 30 times (default); a redirect loop where URL A → URL B → URL A indefinitely; a misconfigured server with circular Location headers; a login flow with many intermediary redirects.

Common situations: Server-side redirect loop after authentication changes; cookie handling causing repeated redirects to a login page; migrating URLs without updating redirect chains; CDN misconfiguration creating circular redirects between HTTP and HTTPS.

Related errors


AI-assisted analysis of psf/requests@8068356288 (2026-08-11). Data as JSON: /api/errors/86a3b690b49e5a2a. Report an issue: GitHub.

Appendix: 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 8068356288)