Nobody has started this yet — be first.
Business impact
The entire reason GET /notifications/stream sends a heartbeat comment on a schedule is to stay well under the idle-connection timeout of whatever sits between the browser and this app -- a load balancer, an nginx reverse proxy, a corporate firewall. The comment right above the setting spells out the math: the shortest common default out there is GCP's 30s backend timeout, and 15s was chosen specifically to leave a 2x margin against that. Someone changed the number without redoing the arithmetic, and the new value blows straight past the exact timeout it was supposed to leave headroom against. The first customer sitting behind a GCP HTTP(S) load balancer will see their notification stream silently die and reconnect over and over -- and because heartbeats are invisible SSE comment lines, nothing in the browser console or server logs points at "heartbeat interval too long" as the cause.
Problem
Settings.heartbeat_interval_seconds defaults to 50.0, larger than the 30-second GCP load-balancer timeout the comment directly above it calls out by name, and leaves virtually no margin against nginx/ALB's 60s defaults either.
Current behavior
Settings().heartbeat_interval_seconds currently returns 50.0, even though the comment two lines above it promises a 2x safety margin against the shortest referenced timeout (GCP's 30s) -- i.e. it should be at most 15.0.
Expected behavior
The default should actually satisfy its own documented margin: comfortably under half of the shortest timeout referenced (GCP's 30s), matching the rationale already written in the comment above the field.
Steps to reproduce
cd fastapi/sse_notificationspython3 -c "from app.config import Settings; print(Settings().heartbeat_interval_seconds)"50.0 -- compare against the "2x safety margin against GCP's 30s" comment directly above the field in app/config.py.Why this matters
Config defaults drift from the comments explaining them more often than code drifts from its own logic, because nothing forces the two to stay in sync -- no test, type check, or linter rule catches "this number no longer satisfies the sentence above it." Only careful review, or a test that encodes the promise numerically instead of leaving it as prose, catches it.
Suggested approach
Read the comment block above heartbeat_interval_seconds in app/config.py again, specifically the sentence about a 2x safety margin against the shortest referenced timeout (GCP's 30s), and pick a value that's actually true to it.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_01_heartbeat_default_margin.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/heartbeat-default-violates-marginFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/heartbeat-default-violates-marginSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8000http://localhost:8001Authorization: Bearer <user_id>X-User-Id: <user_id>curl localhost:8000/healthz
curl -N -H "Authorization: Bearer alice" localhost:8000/notifications/stream
# watch `: heartbeat` lines arrive roughly every 15s with no other traffic
Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_heartbeat_default_margin.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Tickets 14 and 15 need genuinely concurrent, long-lived streams, which the ASGI test transport used everywhere else in this project can't represent -- their tests spin up a real (short-lived) uvicorn server on a loopback socket instead (LiveServer in practice_tickets/tests/conftest.py).
Level 1
Fix a bug
Read existing behaviour, correct it.