The Fix
Upgrade to version 0.13.0 or later.
Based on closed encode/httpx issue #96 · PR/commit linked
Production note: Watch p95/p99 latency and retry volume; timeouts can turn into retry storms and duplicate side-effects.
@@ -4,6 +4,7 @@
from ..concurrency.base import BaseSocketStream, ConcurrencyBackend, TimeoutFlag
from ..config import TimeoutConfig, TimeoutTypes
+from ..exceptions import ConnectionClosed, ProtocolError
from ..models import AsyncRequest, AsyncResponse
from ..utils import get_logger
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse
import http3
app = FastAPI()
http_client = http3.AsyncClient()
@app.middleware('http')
async def sso_middleware(request: Request, call_next):
r = await http_client.post('http://127.0.0.1:8001')
if r.status_code != 200:
return JSONResponse({'ok': 0, 'data': {'status_code': r.status_code}})
ret = r.json()
await r.close()
print(ret)
response = await call_next(request)
return response
@app.get('/')
def index(request: Request):
return {"ok": 1, "data": "welcome to test app!"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
pass
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Upgrade to fixed release\nUpgrade to version 0.13.0 or later.\nWhen NOT to use: Do not apply this fix if the application relies on the original behavior of the connection handling.\n\n
Why This Fix Works in Production
- Trigger: timeout=timeout,
- Mechanism: The error occurs when the server closes the connection unexpectedly during response handling
- Why the fix works: Addresses the RemoteProtocolError by raising a new ConnectionClosed exception if the socket is closed, otherwise reusing the ProtocolError exception. (first fixed release: 0.13.0).
- If left unfixed, tail latency can spike under load and surface as timeouts/retries (amplifying incident impact).
Why This Breaks in Prod
- Shows up under Python 3.7 in real deployments (not just unit tests).
- The error occurs when the server closes the connection unexpectedly during response handling
- Surfaces as: File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 365, in post
Proof / Evidence
- GitHub issue: #96
- Fix PR: https://github.com/encode/httpx/pull/524
- First fixed release: 0.13.0
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.75
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.23
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“There are two web servers”
“I can reproduce it”
“Relevant snippet from the h11 docs: > Connection.next_event Raises: RemoteProtocolError – The peer has misbehaved. You should close the connection (possibly after sending some kind…”
“I think this is caused by the client code in http3.”
Failure Signature (Search String)
- timeout=timeout,
Error Message
Stack trace
Error Message
-------------
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 365, in post
timeout=timeout,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 497, in request
timeout=timeout,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 112, in send
allow_redirects=allow_redirects,
File "/project/venv/lib/python3.7/site-packages/http3/client.py", line 145, in send_handling_redirects
request, verify=verify, cert=cert, timeout=timeout
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection_pool.py", line 121, in send
raise exc
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection_pool.py", line 116, in send
request, verify=verify, cert=cert, timeout=timeout
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/connection.py", line 59, in send
response = await self.h11_connection.send(request, timeout=timeout)
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/http11.py", line 65, in send
event = await self._receive_event(timeout)
File "/project/venv/lib/python3.7/site-packages/http3/dispatch/http11.py", line 109, in _receive_event
event = self.h11_state.next_event()
File "/project/venv/lib/python3.7/site-packages/h11/_connection.py", line 439, in next_event
exc._reraise_as_remote_protocol_error()
File "/project/venv/lib/pyth
... (truncated) ...
Stack trace
Error Message
-------------
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 370, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\errors.py", line 122, in __call__
raise exc from None
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\errors.py", line 100, in __call__
await self.app(scope, receive, _send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\base.py", line 25, in __call__
response = await self.dispatch_func(request, self.call_next)
File "subapp.py", line 56, in sso_middleware
'code': auth_code
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\http3\client.py", line 406, in post
timeout=timeout,
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\http3\client.py", line 548, in request
timeout=timeout,
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\http3\client.py", line 145, in send
allow_redi
... (truncated) ...
Stack trace
Error Message
-------------
INFO: ('127.0.0.1', 14501) - "GET / HTTP/1.1" 200
{'ok': 1, 'data': 'welcome to test app 11111111111!'}
INFO: ('127.0.0.1', 14501) - "GET / HTTP/1.1" 200
INFO: ('127.0.0.1', 14553) - "GET / HTTP/1.1" 500
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 370, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\errors.py", line 122, in __call__
raise exc from None
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\errors.py", line 100, in __call__
await self.app(scope, receive, _send)
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\starlette\middleware\base.py", line 25, in __call__
response = await self.dispatch_func(request, self.call_next)
File "testapp.py", line 13, in sso_middleware
r = await http_client.post('http://127.0.0.1:8001')
File "C:\Users\lch\.virtualenvs\authcenter-5r0y-06W\lib\site-packages\http3\client.py", line 406, in post
timeout=timeout,
File "C:\User
... (truncated) ...
Minimal Reproduction
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse
import http3
app = FastAPI()
http_client = http3.AsyncClient()
@app.middleware('http')
async def sso_middleware(request: Request, call_next):
r = await http_client.post('http://127.0.0.1:8001')
if r.status_code != 200:
return JSONResponse({'ok': 0, 'data': {'status_code': r.status_code}})
ret = r.json()
await r.close()
print(ret)
response = await call_next(request)
return response
@app.get('/')
def index(request: Request):
return {"ok": 1, "data": "welcome to test app!"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
pass
Environment
- Python: 3.7
What Broke
Intermittent errors during load testing led to failed HTTP requests and potential service disruptions.
Why It Broke
The error occurs when the server closes the connection unexpectedly during response handling
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
Upgrade to version 0.13.0 or later.
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Fix reference: https://github.com/encode/httpx/pull/524
First fixed release: 0.13.0
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- Do not apply this fix if the application relies on the original behavior of the connection handling.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
- Upgrade behind a canary and run integration tests against the canary before 100% rollout.
- Make timeouts explicit and test them (unit + integration) to avoid silent behavior changes.
- Instrument retries (attempt count + reason) and alert on spikes to catch dependency slowdowns.
Version Compatibility Table
| Version | Status |
|---|---|
| 0.13.0 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.