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
- Pass an instance of httpx2.BaseTransport (e.g. httpx2.HTTPTransport()) as transport
- If migrating from httpx, replace httpx.* imports with httpx2.* for the transport object
- Use CavemanAsyncOpenAITransport with an httpx2.AsyncBaseTransport for async clients
- 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
- Import transports from httpx2, never httpx, in middleware wiring
- Use AsyncHTTPTransport with CavemanAsyncOpenAITransport and HTTPTransport with the sync one
- Assert transport type in a startup self-test
- After httpx->httpx2 migrations, grep for leftover httpx transport constructors
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
- Expected an HTTPX2 AsyncBaseTransport
- Agno scope resolver must return a Caveman Scope
- AutoGen requires a stable Caveman Scope for each agent or…
- Expected a native Strands Model
- Expected a native ToolSelection
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)