Jump to solution
Verify

The Fix

Upgrade to version 0.20.0 or later.

Based on closed Kludex/uvicorn issue #596 · PR/commit linked

Jump to Verify Open PR/Commit
@@ -82,7 +82,7 @@ plugins = [coverage:report] precision = 2 -fail_under = 97.82 +fail_under = 97.92 show_missing = true
repro.py
import asyncio async def app(scope, receive, send): assert scope["type"] == "websocket" event = await receive() if event["type"] == "websocket.connect": await asyncio.sleep(10) await send({"type": "websocket.accept"})
verify
Re-run: uvicorn debug.app:app --ws wsproto
fix.md
Option A — Upgrade to fixed release\nUpgrade to version 0.20.0 or later.\nWhen NOT to use: Do not use this fix if the application requires immediate disconnection without waiting for handshake completion.\n\n

Why This Fix Works in Production

  • Trigger: LocalProtocolError: Event CloseConnection(code=1012, reason=None) cannot be sent during the handshake
  • Mechanism: The handshake was not completed before attempting to send a frame during shutdown
  • Why the fix works: Checks if the handshake is completed before sending a frame on wsproto shutdown, addressing the issue of LocalProtocolError during shutdown. (first fixed release: 0.20.0).

Why This Breaks in Prod

  • Shows up under Python 3.9 in real deployments (not just unit tests).
  • The handshake was not completed before attempting to send a frame during shutdown
  • Surfaces as: LocalProtocolError: Event CloseConnection(code=1012, reason=None) cannot be sent during the handshake

Proof / Evidence

  • GitHub issue: #596
  • Fix PR: https://github.com/kludex/uvicorn/pull/1737
  • First fixed release: 0.20.0
  • Reproduced locally: No (not executed)
  • Last verified: 2026-02-09
  • Confidence: 0.95
  • Did this fix it?: Yes (upstream fix exists)
  • Own content ratio: 0.30

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“@euri10 Yes, it happens every time we send TERM to gunicorn master process in our production environment”
@MisLink · 2020-03-21 · repro detail · source
“The minimal app: 1”
@MisLink · 2020-03-21 · repro detail · source
“on a side note thanks for websocat, it rocks”
@euri10 · 2020-03-21 · source
“sorry I cant reproduce, am I doing then in another terminal and before the 10s in a 3rd one:”
@euri10 · 2020-03-21 · source

Failure Signature (Search String)

  • LocalProtocolError: Event CloseConnection(code=1012, reason=None) cannot be sent during the handshake

Error Message

Stack trace
error.txt
Error Message ------------- LocalProtocolError: Event CloseConnection(code=1012, reason=None) cannot be sent during the handshake File "gunicorn/arbiter.py", line 583, in spawn_worker worker.init_process() File "uvicorn/workers.py", line 57, in init_process super(UvicornWorker, self).init_process() File "gunicorn/workers/base.py", line 140, in init_process self.run() File "uvicorn/workers.py", line 66, in run loop.run_until_complete(server.serve(sockets=self.sockets)) File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete File "uvicorn/main.py", line 403, in serve await self.shutdown(sockets=sockets) File "uvicorn/main.py", line 539, in shutdown connection.shutdown() File "uvicorn/protocols/websockets/wsproto_impl.py", line 115, in shutdown output = self.conn.send(wsproto.events.CloseConnection(code=1012)) File "__init__.py", line 61, in send data += self.handshake.send(event) File "wsproto/handshake.py", line 101, in send "Event {} cannot be sent during the handshake".format(event)
Stack trace
error.txt
Error Message ------------- $ uvicorn debug.app:app --ws wsproto INFO: Started server process [91950] INFO: Waiting for application startup. INFO: ASGI 'lifespan' protocol appears unsupported. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) Go ahead, stop the server... ^CINFO: Shutting down Traceback (most recent call last): File "/Users/florimond/Developer/python-projects/uvicorn/venv/bin/uvicorn", line 33, in <module> sys.exit(load_entry_point('uvicorn', 'console_scripts', 'uvicorn')()) File "/Users/florimond/Developer/python-projects/uvicorn/venv/lib/python3.9/site-packages/click/core.py", line 829, in __call__ return self.main(*args, **kwargs) File "/Users/florimond/Developer/python-projects/uvicorn/venv/lib/python3.9/site-packages/click/core.py", line 782, in main rv = self.invoke(ctx) File "/Users/florimond/Developer/python-projects/uvicorn/venv/lib/python3.9/site-packages/click/core.py", line 1066, in invoke return ctx.invoke(self.callback, **ctx.params) File "/Users/florimond/Developer/python-projects/uvicorn/venv/lib/python3.9/site-packages/click/core.py", line 610, in invoke return callback(*args, **kwargs) File "/Users/florimond/Developer/python-projects/uvicorn/uvicorn/main.py", line 362, in main run(**kwargs) File "/Users/florimond/Developer/python-projects/uvicorn/uvicorn/m ... (truncated) ...

Minimal Reproduction

repro.py
import asyncio async def app(scope, receive, send): assert scope["type"] == "websocket" event = await receive() if event["type"] == "websocket.connect": await asyncio.sleep(10) await send({"type": "websocket.accept"})

Environment

  • Python: 3.9

What Broke

LocalProtocolError occurs during shutdown, causing websocket connections to fail.

Why It Broke

The handshake was not completed before attempting to send a frame during shutdown

Fix Options (Details)

Option A — Upgrade to fixed release Safe default (recommended)

Upgrade to version 0.20.0 or later.

When NOT to use: Do not use this fix if the application requires immediate disconnection without waiting for handshake completion.

Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.

Fix reference: https://github.com/kludex/uvicorn/pull/1737

First fixed release: 0.20.0

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • Do not use this fix if the application requires immediate disconnection without waiting for handshake completion.

Verify Fix

verify
Re-run: uvicorn debug.app:app --ws wsproto

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

VersionStatus
0.20.0 Fixed

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.