Jump to solution
Verify

The Fix

Fixes the 'No response returned' RuntimeError in BaseHTTPMiddleware by preventing polling for disconnects in StreamingResponse.

Based on closed Kludex/starlette issue #2516 · PR/commit linked

Production note: This tends to surface only under concurrency. Reproduce with load tests and watch for lock contention/cancellation paths.

Jump to Verify Open PR/Commit
@@ -6,9 +6,8 @@ from starlette._utils import collapse_excgroups -from starlette.background import BackgroundTask from starlette.requests import ClientDisconnect, Request -from starlette.responses import ContentStream, Response, StreamingResponse
repro.py
import asyncio from hypercorn.asyncio import serve from hypercorn.config import Config from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response from starlette.middleware.base import RequestResponseEndpoint from starlette.middleware import Middleware class DummyMiddleware(BaseHTTPMiddleware): async def dispatch( self, request: Request, call_next: RequestResponseEndpoint ) -> Response: return await call_next(request) async def homepage(request): return JSONResponse({}) app = Starlette( routes=[ Route("/", homepage), ], middleware=[Middleware(DummyMiddleware) for i in range(100)], # Yes, 100x the same middleware. ) config = Config.from_mapping({}) config.bind = ["127.0.0.1:8000"] asyncio.run(serve(app, config))
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Apply the official fix\nFixes the 'No response returned' RuntimeError in BaseHTTPMiddleware by preventing polling for disconnects in StreamingResponse.\nWhen NOT to use: This fix should not be applied if the application relies on the previous behavior of BaseHTTPMiddleware.\n\n

Why This Fix Works in Production

  • Trigger: `RuntimeError("No response returned")` in `BaseHTTPMiddleware`
  • Mechanism: The error occurs due to a race condition in BaseHTTPMiddleware when handling client disconnects
Production impact:
  • If left unfixed, failures can be intermittent under concurrency (hard to reproduce; shows up as sporadic 5xx/timeouts).

Why This Breaks in Prod

  • Shows up under Python 3.11 in real deployments (not just unit tests).
  • The error occurs due to a race condition in BaseHTTPMiddleware when handling client disconnects
  • Surfaces as: `RuntimeError("No response returned")` in `BaseHTTPMiddleware`

Proof / Evidence

Discussion

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

“We were seeing this as well on py3.12, dep versions: I'm downgrading us to: Which seems like it'll resolve the issue, but I'll report back…”
@adamsanghera · 2024-06-04 · confirmation · source
“This fix introduced a backward incompatible change (https://github.com/encode/starlette/pull/2620). Fixed that in https://github.com/encode/starlette/pull/2687. Ensured the original issue is resolved. @Kludex @adriangb please review. Thank you”
@dmitry-mli · 2024-09-06 · confirmation · source
“FYI: We’ve reproduced the issue using starlette==0.47.2, sse-starlette==2.4.1 and fastapi==0.116.1; the client is a mobile app. Sending a message from the server at the start…”
@yetanotherion · 2025-07-30 · confirmation · source
“Hi, seems like we encountered a similar issue here”
@XieJiSS · 2024-03-05 · repro detail · source

Failure Signature (Search String)

  • `RuntimeError("No response returned")` in `BaseHTTPMiddleware`

Error Message

Stack trace
error.txt
Error Message ------------- `RuntimeError("No response returned")` in `BaseHTTPMiddleware`

Minimal Reproduction

repro.py
import asyncio from hypercorn.asyncio import serve from hypercorn.config import Config from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response from starlette.middleware.base import RequestResponseEndpoint from starlette.middleware import Middleware class DummyMiddleware(BaseHTTPMiddleware): async def dispatch( self, request: Request, call_next: RequestResponseEndpoint ) -> Response: return await call_next(request) async def homepage(request): return JSONResponse({}) app = Starlette( routes=[ Route("/", homepage), ], middleware=[Middleware(DummyMiddleware) for i in range(100)], # Yes, 100x the same middleware. ) config = Config.from_mapping({}) config.bind = ["127.0.0.1:8000"] asyncio.run(serve(app, config))

Environment

  • Python: 3.11

What Broke

Clients experience intermittent 'No response returned' errors during high load conditions.

Why It Broke

The error occurs due to a race condition in BaseHTTPMiddleware when handling client disconnects

Fix Options (Details)

Option A — Apply the official fix

Fixes the 'No response returned' RuntimeError in BaseHTTPMiddleware by preventing polling for disconnects in StreamingResponse.

When NOT to use: This fix should not be applied if the application relies on the previous behavior of BaseHTTPMiddleware.

Fix reference: https://github.com/encode/starlette/pull/2620

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

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix should not be applied if the application relies on the previous behavior of BaseHTTPMiddleware.

Verify Fix

verify
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 stress test that runs high-concurrency workloads and fails on thread dumps / blocked locks.
  • Enable watchdog dumps in prod (faulthandler, thread dump endpoint) to capture deadlocks quickly.

Related Issues

No related fixes found.

Sources

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