The Fix
Based on closed Kludex/uvicorn issue #1618 · 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%.
@@ -92,7 +92,6 @@ def __init__(
self.connections = server_state.connections
self.tasks = server_state.tasks
- self.default_headers = server_state.default_headers
# Per-connection state
import asyncio
from email.utils import parsedate_to_datetime
from contextlib import asynccontextmanager
import httpx
from uvicorn import Config, Server
async def test_default_date_header_different():
config = Config(app=app, loop="asyncio", limit_max_requests=2)
async with run_server(config):
async with httpx.AsyncClient() as client:
response1 = await client.get("http://127.0.0.1:8000")
await asyncio.sleep(2)
response2 = await client.get("http://127.0.0.1:8000")
response1_date = parsedate_to_datetime(response1.headers["date"])
response2_date = parsedate_to_datetime(response2.headers["date"])
assert(response2_date > response1_date)
async def app(scope, receive, send):
assert scope["type"] == "http"
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
@asynccontextmanager
async def run_server(config: Config):
server = Server(config=config)
cancel_handle = asyncio.ensure_future(server.serve())
await asyncio.sleep(0.1)
try:
yield server
finally:
await server.shutdown()
cancel_handle.cancel()
asyncio.run(test_date_header_different())
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Why This Fix Works in Production
- Trigger: "Date" header not changing between requests
- Mechanism: The 'Date' header was not updated for each request when using a persistent connection
- 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
- Shows up under Python 3.10.5 in real deployments (not just unit tests).
- The 'Date' header was not updated for each request when using a persistent connection
- Production symptom (often without a traceback): "Date" header not changing between requests
Proof / Evidence
- GitHub issue: #1618
- Fix PR: https://github.com/kludex/uvicorn/pull/1706
- Reproduced locally: No (not executed)
- Last verified: 2026-02-12
- Confidence: 0.70
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.39
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“> not sure about that, isn't that the keep-alive of 5s is for, the tests pass fine if for instance we set > > Correct.…”
“This will be available in uvicorn 0.19.0.”
“not sure about that, isn't that the keep-alive of 5s is for, the tests pass fine if for instance we set”
“@euri10 that would mean the connection expires on TimeOut right? The discussion here was mainly focusing on repeated request using same connection. The discussion has…”
Failure Signature (Search String)
- "Date" header not changing between requests
- I would expect that the "Date" header to change between requests, even when using an existing connection.
Copy-friendly signature
Failure Signature
-----------------
"Date" header not changing between requests
I would expect that the "Date" header to change between requests, even when using an existing connection.
Error Message
Signature-only (no traceback captured)
Error Message
-------------
"Date" header not changing between requests
I would expect that the "Date" header to change between requests, even when using an existing connection.
Minimal Reproduction
import asyncio
from email.utils import parsedate_to_datetime
from contextlib import asynccontextmanager
import httpx
from uvicorn import Config, Server
async def test_default_date_header_different():
config = Config(app=app, loop="asyncio", limit_max_requests=2)
async with run_server(config):
async with httpx.AsyncClient() as client:
response1 = await client.get("http://127.0.0.1:8000")
await asyncio.sleep(2)
response2 = await client.get("http://127.0.0.1:8000")
response1_date = parsedate_to_datetime(response1.headers["date"])
response2_date = parsedate_to_datetime(response2.headers["date"])
assert(response2_date > response1_date)
async def app(scope, receive, send):
assert scope["type"] == "http"
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
@asynccontextmanager
async def run_server(config: Config):
server = Server(config=config)
cancel_handle = asyncio.ensure_future(server.serve())
await asyncio.sleep(0.1)
try:
yield server
finally:
await server.shutdown()
cancel_handle.cancel()
asyncio.run(test_date_header_different())
Environment
- Python: 3.10.5
What Broke
Responses were incorrectly cached due to a static 'Date' header, leading to stale data being served.
Why It Broke
The 'Date' header was not updated for each request when using a persistent connection
Fix Options (Details)
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/1706
Last verified: 2026-02-12. Validate in your environment.
When NOT to Use This Fix
- This fix should not be applied if the application relies on the 'Date' header being static for caching purposes.
- 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 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
- 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.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.