JuliusBrussee/caveman · error · TypeError

Expected an HTTPX2 BaseTransport

Error message

Expected an HTTPX2 BaseTransport

What it means

CavemanOpenAITransport wraps an existing httpx2 transport so OpenAI calls can be intercepted while everything else passes through untouched. It validates at construction time that the supplied object is actually an httpx2.BaseTransport, since it delegates handle_request to it.

Solutions

  1. Pass an instance of httpx2.BaseTransport (e.g. httpx2.HTTPTransport()) as transport
  2. If migrating from httpx, replace httpx.* imports with httpx2.* for the transport object
  3. Use CavemanAsyncOpenAITransport with an httpx2.AsyncBaseTransport for async clients
  4. Check the object with isinstance before constructing

Example fix

# before
transport = CavemanOpenAITransport(httpx.HTTPTransport())  # TypeError
# after
transport = CavemanOpenAITransport(httpx2.HTTPTransport())
client = with_caveman_openai(httpx2.Client(transport=transport))
Defensive patterns

Strategy: type-guard

Validate before calling

def ensure_sync_transport(transport):
    import caveman_middleware._httpx2 as m
    if not isinstance(transport, m.httpx2.BaseTransport):
        raise TypeError("transport must be an httpx2.BaseTransport")
    return transport

Type guard

def is_sync_transport(transport) -> bool:
    return isinstance(transport, httpx2.BaseTransport)

Try / catch

try:
    transport = CavemanOpenAITransport(candidate)
except TypeError as e:
    if "BaseTransport" in str(e):
        candidate = httpx2.HTTPTransport()
        transport = CavemanOpenAITransport(candidate)

Prevention

When it happens

Trigger: Constructing CavemanOpenAITransport(None), with an httpx (v1) transport instead of httpx2, with a raw HTTPAdapter/requests object, or with a client instead of a transport.

Common situations: Mixing httpx and httpx2 imports after a migration; passing httpx.AsyncHTTPTransport into the sync wrapper; copying examples from the httpx (non-2) docs; passing the transport= argument of httpx2.Client wrongly wrapped.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/_httpx2.py:41

            original.logical_call_id, str(uuid.uuid4()), optimization=original.optimization, plan_id=original.plan_id)
        self.attempt_count += 1
        try:
            attempt.wire_sha256 = hashlib.sha256(request.content).hexdigest()
        except httpx2.RequestNotRead:
            pass
        return attempt


class CavemanOpenAITransport(httpx2.BaseTransport):
    """Wrap an application-owned HTTPX2 transport before client construction.

    Pass this same object as ``transport`` to ``with_caveman_openai``. Requests
    outside that native wrapper pass through. The supplied transport retains its
    connection pool, TLS/proxy settings, and close behavior; this adds no retries.
    """
    def __init__(self, transport):
        if not isinstance(transport, httpx2.BaseTransport):
            raise TypeError("Expected an HTTPX2 BaseTransport")
        self.transport = transport

    def handle_request(self, request):
        call = owner.get()
        if not isinstance(call, OpenAICall) or call.transport is not self:
            return self.transport.handle_request(request)
        attempt = call.next_attempt(request)
        attempt.observe("dispatch_intent")
        token = owner.set(attempt)
        try:
            response = self.transport.handle_request(request)
            observe_response(response, attempt)
            return response
        except BaseException:
            attempt.observe("failed")
            raise
        finally:
            owner.reset(token)

View on GitHub (pinned to 3ee70a1026)