Nobody has started this yet — be first.
Business impact
When a batch fails every one of its retry attempts, _flush_with_retry gives up and calls record_dropped(batch) — but that function is a no-op stub, so the batch's events are gone with nothing but a log line as any trace they ever existed. This is called out in the project's own README as a known, accepted limitation; closing it means an operator or automated job can inspect what was dropped instead of losing it irrecoverably.
Problem
app/deadletter.py::record_dropped and get_dropped are no-op stubs: record_dropped does nothing, and get_dropped always returns an empty list, so a permanently-failed batch leaves no retrievable trace at all.
Current behavior
Flushing a batch against an inserter that fails every attempt causes _flush_with_retry to call record_dropped(batch), but get_dropped() afterward still returns [].
Expected behavior
A batch that exhausts all retries is retrievable afterward via get_dropped(), with its events intact; multiple dropped batches from separate failures all appear, not just the most recent.
Steps to reproduce
pytest practice_tickets/tests/test_ticket_19_dead_letter.py -v — flushes a batch of 2 events against an inserter that fails every attempt (flush_max_retries=1), then checks get_dropped(), which returns [] instead of the dropped batch.Why this matters
_flush_with_retry's decision to drop a batch after exhausting retries is deliberate and correct (blocking a flush worker forever on one wedged batch would starve every other event behind it in the queue) — the problem isn't the drop, it's that dropping currently means "gone with no trace." A minimal, retrievable record of what was dropped turns an unrecoverable loss into a recoverable one.
Suggested approach
Look at app/deadletter.py — record_dropped and get_dropped are already the two functions app/flush.py expects to exist (the call site is already wired in _flush_with_retry's give-up branch). Think about what module-level state record_dropped needs to hold onto so get_dropped can hand it back later.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_19_dead_letter.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 feat/dead-letter-dropped-batchesFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/dead-letter-dropped-batchesSubmit 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:8000localhost:5432curl localhost:8000/healthzcurl localhost:8000/readyzWork the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_persisted_count.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 20, clean pass/fail summary
TICKET-17 needs a real Postgres to run at all (it checks which column a value lands in, which no fake/in-memory DB backend can meaningfully verify) — set TEST_DATABASE_URL=postgresql://<you>@localhost/ingest_test before running it or the full suite; without it, that one test is skipped, not failed.
Level 2
Implement a feature
Extend the system within its own patterns.