The Fix
Upgrade to version 0.13.1 or later.
Based on closed Kludex/uvicorn issue #244 · PR/commit linked
Production note: This usually shows up under retries/timeouts. Treat it as a side-effect risk until you can verify behavior with a canary + real traffic.
@@ -216,6 +216,7 @@ async def asgi_send(self, message):
elif message_type == "websocket.close":
code = message.get("code", 1000)
+ self.close_code = code # for WebSocketServerProtocol
await self.close(code)
self.closed_event.set()
class Application:
def __init__(self, scope):
self.scope = scope
async def __call__(self, receive, send):
if self.scope["type"] != "websocket":
return
while True:
# what for the 'websocket.connect' event.
await receive()
# Close the connection
await send({'type': 'websocket.close'})
Re-run: uvicorn asgi:Application --ws websockets
Option A — Upgrade to fixed release\nUpgrade to version 0.13.1 or later.\nWhen NOT to use: This fix should not be applied if the application logic requires immediate connection closure.\n\nOption C — Workaround\nfor this? I can post more info about what I'm seeing, but it looks like the connection closes and things are fine browser side, but if I call .close() in the .connect() function I end up with type errors in the logs. Having stack traces in the logs is obviously not ideal, but it would help to clarify that's the extent of the issue...\nWhen NOT to use: This fix should not be applied if the application logic requires immediate connection closure.\n\n
Why This Fix Works in Production
- Trigger: result = await asgi(self.asgi_receive, self.asgi_send)
- Mechanism: The TypeError occurs when attempting to receive data from a closed WebSocket connection
- Why the fix works: Fix failures when ASGI app rejects a connection during handshake, addressing TypeError when connection is closed. (first fixed release: 0.13.1).
Why This Breaks in Prod
- Shows up under Python 3.7 in real deployments (not just unit tests).
- The TypeError occurs when attempting to receive data from a closed WebSocket connection
- Surfaces as: Traceback (most recent call last):
Proof / Evidence
- GitHub issue: #244
- Fix PR: https://github.com/kludex/uvicorn/pull/704
- First fixed release: 0.13.1
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.85
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.27
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“> The thing that would help most with progressing this would be to try to replicate the issue in a plain ASGI application, or in…”
“> If anyone's able to reduce the issue like that then I'd be very happy to dig into it”
“Here is a minimal example without using django: To test it, you have to start it with an asgi server an open a websocket connection”
“The thing that would help most with progressing this would be to try to replicate the issue in a plain ASGI application, or in Starlette,…”
Failure Signature (Search String)
- result = await asgi(self.asgi_receive, self.asgi_send)
Error Message
Stack trace
Error Message
-------------
Traceback (most recent call last):
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 140, in run_asgi
result = await asgi(self.asgi_receive, self.asgi_send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/sessions.py", line 179, in __call__
return await self.inner(receive, self.send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/middleware.py", line 41, in coroutine_call
await inner_instance(receive, send)
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/consumer.py", line 59, in __call__
[receive, self.channel_receive], self.dispatch
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/utils.py", line 59, in await_many_dispatch
await task
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/channels/utils.py", line 51, in await_many_dispatch
result = task.result()
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 222, in asgi_receive
data = await self.recv()
File "/home/ossi/src/channels-examples/.venv/lib/python3.7/site-packages/websockets/protocol.py", line 419, in recv
return_when=asyncio.FIRST_COMPLETED,
File "/usr/lib/py
... (truncated) ...
Stack trace
Error Message
-------------
TypeError: An asyncio.Future, a coroutine or an awaitable is required
at ensure_future (/usr/local/lib/python3.6/asyncio/tasks.py:526)
at <setcomp> (/usr/local/lib/python3.6/asyncio/tasks.py:311)
at wait (/usr/local/lib/python3.6/asyncio/tasks.py:311)
at recv (/usr/local/lib/python3.6/site-packages/websockets/protocol.py:419)
at asgi_receive (/usr/local/lib/python3.6/site-packages/uvicorn/protocols/websockets/websockets_impl.py:227)
at await_many_dispatch (/usr/local/lib/python3.6/site-packages/channels/utils.py:51)
at await_many_dispatch (/usr/local/lib/python3.6/site-packages/channels/utils.py:59)
at __call__ (/usr/local/lib/python3.6/site-packages/channels/consumer.py:59)
at run_asgi (/usr/local/lib/python3.6/site-packages/uvicorn/protocols/websockets/websockets_impl.py:147)
Stack trace
Error Message
-------------
$ uvicorn asgi:Application --ws websockets
INFO: Started server process [23052]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: ('127.0.0.1', 58172) - "WebSocket /" 403
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/ossi/src/uvicorn_test/venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 147, in run_asgi
result = await asgi(self.asgi_receive, self.asgi_send)
File "./asgi.py", line 12, in __call__
await receive()
File "/home/ossi/src/uvicorn_test/venv/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 227, in asgi_receive
data = await self.recv()
File "/home/ossi/src/uvicorn_test/venv/lib/python3.7/site-packages/websockets/protocol.py", line 419, in recv
return_when=asyncio.FIRST_COMPLETED,
File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in wait
fs = {ensure_future(f, loop=loop) for f in set(fs)}
File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in <setcomp>
fs = {ensure_future(f, loop=loop) for f in set(fs)}
File "/usr/lib/python3.7/asyncio/tasks.py", line 592, in ensure_future
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required
^CINFO: Shutting down
INFO: Waiting for app
... (truncated) ...
Minimal Reproduction
class Application:
def __init__(self, scope):
self.scope = scope
async def __call__(self, receive, send):
if self.scope["type"] != "websocket":
return
while True:
# what for the 'websocket.connect' event.
await receive()
# Close the connection
await send({'type': 'websocket.close'})
Environment
- Python: 3.7
What Broke
Uvicorn raises TypeError during WebSocket connection closure, leading to log clutter.
Why It Broke
The TypeError occurs when attempting to receive data from a closed WebSocket connection
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
Upgrade to version 0.13.1 or later.
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Option C — Workaround Temporary workaround
for this? I can post more info about what I'm seeing, but it looks like the connection closes and things are fine browser side, but if I call .close() in the .connect() function I end up with type errors in the logs. Having stack traces in the logs is obviously not ideal, but it would help to clarify that's the extent of the issue...
Use only if you cannot change versions today. Treat this as a stopgap and remove once upgraded.
Option D — Guard side-effects with OnceOnly Guardrail for side-effects
Mitigate duplicate external side-effects under retries/timeouts/agent loops by gating the operation before calling external systems.
- Place OnceOnly between your code/agent and real side-effects (Stripe, emails, CRM, APIs).
- Use a stable key per side-effect (e.g., customer_id + action + idempotency_key).
- Fail-safe: configure fail-open vs fail-closed based on blast radius and spend risk.
Show example snippet (optional)
from onceonly import OnceOnly
import os
once = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"], fail_open=True)
# Stable idempotency key per real side-effect.
# Use a request id / job id / webhook delivery id / Stripe event id, etc.
event_id = "evt_..." # replace
key = f"stripe:webhook:{event_id}"
res = once.check_lock(key=key, ttl=3600)
if res.duplicate:
return {"status": "already_processed"}
# Safe to execute the side-effect exactly once.
handle_event(event_id)
Fix reference: https://github.com/kludex/uvicorn/pull/704
First fixed release: 0.13.1
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- This fix should not be applied if the application logic requires immediate connection closure.
- Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.
Verify Fix
Re-run: uvicorn asgi:Application --ws websockets
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a TLS smoke test that performs a real handshake in CI (include CA bundle validation and hostname checks).
- Alert on handshake failures by error string and endpoint to catch cert/CA changes quickly.
Version Compatibility Table
| Version | Status |
|---|---|
| 0.13.1 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.