Nobody has started this yet — be first.
Business impact
The whole point of this service is that a network blip must not silently drop notifications -- a guarantee that depends entirely on the backlog replay branch reading a user's stream of past notifications when a client reconnects with Last-Event-ID. It's currently reading from the pub/sub channel key instead, a key nothing ever writes stream entries to. Redis happily returns an empty result for a range query against a key that was never used as a stream, with no error at all -- so every reconnect-with-replay silently returns nothing, for every user, all the time. A user disconnected for even one second who missed a notification during that second never sees it, with no error, log line, or metric anywhere pointing at why.
Problem
The backlog replay branch reads from channel_key(user_id) -- the pub/sub channel name -- instead of stream_key(user_id), the key publish_event() actually XADDs backlog entries onto.
Current behavior
Publishing 2 notifications and then reconnecting with last_event_id set to something before both of them returns an empty replay, despite both notifications genuinely existing in Redis under the stream key.
Expected behavior
The backlog replay branch must read from the same key publish_event() writes to -- stream_key(user_id).
Steps to reproduce
See practice_tickets/tests/test_ticket_10_replay_reads_wrong_redis_key.py: publish 2 notifications, then start event_stream() with last_event_id set to something before both of them -- the replay comes back empty despite both notifications genuinely existing in Redis.
Why this matters
stream_key and channel_key (app/keys.py) are two one-line functions that look almost identical and sit right next to each other throughout app/streaming.py and app/publisher.py -- reaching for the wrong one at a call site is an easy slip, and there's no type-level distinction between a stream key and a channel key that would catch it (they're both just str). Worse, a range query against a key of the wrong type, or one that doesn't exist, doesn't raise in Redis -- it just returns an empty list, identical to a user genuinely having no backlog.
Suggested approach
Compare the key used in this replay call against the key used in publish_event()'s backlog write (app/publisher.py). They need to be the same key for the same user.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_10_replay_reads_wrong_redis_key.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/replay-reads-wrong-redis-keyFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/replay-reads-wrong-redis-keySubmit 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.