The Fix
Upgrade to version 0.19.1 or later.
Based on closed Kludex/starlette issue #1560 · PR/commit linked
Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.
@@ -80,7 +80,9 @@ async def dispatch(self) -> None:
await self.on_receive(websocket, data)
elif message["type"] == "websocket.disconnect":
- close_code = int(message.get("code", status.WS_1000_NORMAL_CLOSURE))
+ close_code = int(
+ message.get("code") or status.WS_1000_NORMAL_CLOSURE
from starlette.applications import Starlette
from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route, WebSocketRoute
html = """
<!DOCTYPE html>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
</script>
"""
class Homepage(HTTPEndpoint):
async def get(self, request):
return HTMLResponse(html)
class Echo(WebSocketEndpoint):
async def on_connect(self, websocket):
await websocket.close()
routes = [
Route("/", Homepage),
WebSocketRoute("/ws", Echo)
]
app = Starlette(routes=routes)
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.19.1 or later.\nWhen NOT to use: This fix should not be used if the application logic requires a specific close code.\n\n
Why This Fix Works in Production
- Trigger: close_code = int(message.get("code", status.WS_1000_NORMAL_CLOSURE))
- Mechanism: The code was None when the server denied the WebSocket connection, causing a TypeError
- Why the fix works: Avoids a TypeError on websocket disconnect when the code is None by providing a default closure code. (first fixed release: 0.19.1).
- If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.
Why This Breaks in Prod
- The code was None when the server denied the WebSocket connection, causing a TypeError
- Surfaces as: File "starlette/endpoints.py", line 83, in dispatch
Proof / Evidence
- GitHub issue: #1560
- Fix PR: https://github.com/kludex/starlette/pull/1574
- First fixed release: 0.19.1
- 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.45
Verified Execution
We executed the runnable minimal repro in a temporary environment and captured exit codes + logs.
- Status: PASS
- Ran: 2026-02-11T16:52:29Z
- Package: starlette
- Fixed: 0.19.1
- Mode: fixed_only
- Outcome: ok
Logs
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“I can't reproduce this, a minimal example would be very helpful.”
“While creating a minimal example I noticed that the problem only exists when using **wsproto** instead of websockets. Minimal example: Running it with uvicorn ws:app…”
“As per https://asgi.readthedocs.io/en/latest/specs/www.html#disconnect-receive-event-ws (image below), the application should receive the "code" from the server”
Failure Signature (Search String)
- close_code = int(message.get("code", status.WS_1000_NORMAL_CLOSURE))
Error Message
Stack trace
Error Message
-------------
File "starlette/endpoints.py", line 83, in dispatch
close_code = int(message.get("code", status.WS_1000_NORMAL_CLOSURE))
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
Minimal Reproduction
from starlette.applications import Starlette
from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route, WebSocketRoute
html = """
<!DOCTYPE html>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
</script>
"""
class Homepage(HTTPEndpoint):
async def get(self, request):
return HTMLResponse(html)
class Echo(WebSocketEndpoint):
async def on_connect(self, websocket):
await websocket.close()
routes = [
Route("/", Homepage),
WebSocketRoute("/ws", Echo)
]
app = Starlette(routes=routes)
What Broke
WebSocket connection denial leads to a TypeError exception in production.
Why It Broke
The code was None when the server denied the WebSocket connection, causing a TypeError
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
Upgrade to version 0.19.1 or later.
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Fix reference: https://github.com/kludex/starlette/pull/1574
First fixed release: 0.19.1
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- This fix should not be used if the application logic requires a specific close code.
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
- Capture the exact failing error string in logs and tests so you can reproduce via a minimal script.
- Pin production dependencies and upgrade only with a reproducible test that hits the failing path.
Version Compatibility Table
| Version | Status |
|---|---|
| 0.19.1 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.