Nobody has started this yet — be first.
Business impact
redis-py's PubSub.subscribe() deliberately leaves its own subscription-confirmation message sitting in the connection's read queue rather than consuming it. That confirmation is still sitting there the first time event_stream()'s main loop reads a message, and the loop currently can't distinguish it from genuinely nothing having happened, so it emits a heartbeat immediately instead of waiting out a real interval. This inflates the heartbeat counter by exactly one for every single connection regardless of how idle it actually was, quietly corrupting any alerting built on heartbeat rate, and it makes any monitor or test that expects the first thing it reads to be real content see a bogus heartbeat instead -- a source of flaky, hard-to-explain failures in exactly the tooling meant to catch real problems.
Problem
event_stream() no longer drains the subscription-confirmation message redis-py's subscribe() leaves behind, so the very first get_message() call in the main loop reads that leftover confirmation instead of waiting on real traffic.
Current behavior
Connecting with heartbeat_interval=1.0s and nothing else published, the first heartbeat arrives in about 0.002s instead of after a genuine ~1.0s idle wait.
Expected behavior
The subscription confirmation must be drained and discarded once, immediately after subscribe() succeeds, so it never gets mistaken for the connection having been idle for a full heartbeat interval.
Steps to reproduce
See practice_tickets/tests/test_ticket_08_spurious_heartbeat_on_connect.py: connect with nothing else published at all, and measure how long the first heartbeat takes to arrive -- it shows up almost instantly instead of after a genuine heartbeat_interval-length wait.
Why this matters
This is a real gotcha in redis-py's pub/sub API, not an obvious mistake -- subscribe() not consuming its own confirmation is documented but easy to miss, and the fix only makes sense once you know the confirmation is sitting there at all. It's completely invisible in a quick read of event_stream(), since every line individually looks correct.
Suggested approach
Right after await pubsub.subscribe(channel) succeeds, and before anything relies on get_message() meaning "nothing happened," there needs to be exactly one throwaway read that consumes whatever subscribe() left behind. Using this connection's heartbeat_interval as that one call's timeout keeps it correct even if the confirmation takes a moment to arrive.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_08_spurious_heartbeat_on_connect.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/spurious-heartbeat-on-connectFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/spurious-heartbeat-on-connectSubmit 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.