JuliusBrussee/caveman · error · MiddlewareError

redirect_refused

redirect_refused

Error message

redirect_refused

What it means

MiddlewareError raised by the SDK's raw HTTP transport (_http) when the middleware server answers with a 3xx redirect status. The client deliberately refuses to follow redirects so that requests are never silently re-sent to a different host (which could leak tokens or body payloads). This means the configured endpoint URL points at something that redirects instead of serving the API directly.

Solutions

  1. Use the exact middleware API endpoint URL as configured server-side, matching scheme (https) and path so the server responds 200 directly
  2. Check server/proxy config and disable HTTP->HTTPS or host rewrites for the API path, or redirect at the client by changing the configured URL
  3. If a proxy is required, use one that forwards without redirecting, or handle the redirect target manually by updating the endpoint config
  4. Verify with curl -i <endpoint> that no 3xx is returned before the API response

Example fix

// before
client = MiddlewareClient(endpoint="http://mw.internal/api/")  # server redirects to https
// after
client = MiddlewareClient(endpoint="https://mw.internal/api")  # direct, no redirect
Defensive patterns

Strategy: try-catch

Validate before calling

import subprocess
def endpoint_redirects(url):
    r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", url], capture_output=True, text=True)
    return r.stdout.strip().startswith("3")
# call before constructing the client; if True, fix the URL first

Type guard

def is_direct_endpoint(status: int) -> bool:
    return 200 <= status < 300

Try / catch

try:
    client.ready()
except MiddlewareError as e:
    if e.args[0] == "redirect_refused":
        log.error("endpoint redirected; fix base URL scheme/path")
    raise

Prevention

When it happens

Trigger: Any of ready(), optimize(), retrieve(), observe(), or delete_session() performing _http() when the middleware base URL returns 301/302/303/307/308. Typical cause: endpoint behind a load balancer or configured without/with a trailing path that triggers an HTTP->HTTPS or host-rewrite redirect.

Common situations: Configured base URL as http:// while the server forces https://; missing or extra trailing slash on the endpoint; proxy or gateway in front of the middleware issuing redirects; pointing the SDK at a web UI URL instead of the API endpoint.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/99ed450b0a378f18. Report an issue: GitHub.

Appendix: source

Thrown at packages/sdk/python/caveman_cloud/middleware/runtime.py:387

            connection.connect()
            sock = connection.sock

            def bound():
                left = timeout - (time.monotonic() - started)
                if self._closed:
                    raise MiddlewareError("closed")
                if left <= 0:
                    raise MiddlewareError("deadline")
                if sock is not None:
                    sock.settimeout(left)

            bound()
            connection.request("POST" if body is not None else "GET", PREFIX + path,
                               body=body.encode("utf-8") if body is not None else None, headers=headers)
            bound()
            with connection.getresponse() as response:
                if 300 <= response.status < 400:
                    raise MiddlewareError("redirect_refused")
                content = bytearray()
                while True:
                    bound()
                    # read1 plus the remaining socket deadline bounds slow,
                    # chunked bodies without buffering beyond the response cap.
                    part = response.read1(min(65536, (4 << 20) + 1 - len(content)))
                    if not part:
                        break
                    content.extend(part)
                    if len(content) > 4 << 20:
                        raise MiddlewareError("payload_limit")
                data = json.loads(content.decode("utf-8"))
                if not 200 <= response.status < 300:
                    code = data.get("error", {}).get("code") if isinstance(data, dict) else None
                    raise MiddlewareError(code if validate.token(code) else "runtime_unavailable")
                return data
        finally:
            connection.close()

View on GitHub (pinned to 3ee70a1026)