Nobody has started this yet — be first.
Business impact
/readyz is what a load balancer or Kubernetes uses to decide whether to send traffic to this gateway instance. Every rate-limited route depends on Redis being reachable -- if Redis is down, rate limiting either silently fails open or every request 500s, depending on the route. Right now /readyz returns HTTP 200 even when its own Redis ping fails, so nothing upstream of the gateway (load balancer, k8s readiness probe, an on-call dashboard) can tell the difference between "fine" and "broken." A Redis outage becomes an outage that traffic keeps getting routed into, discovered only when customers start complaining -- not a clean, automatic failover.
Problem
GET /readyz always returns HTTP 200, even in the branch that already detects the Redis ping failure and builds a {"status": "degraded", ...} body for it.
Current behavior
readyz() builds the correct "degraded" body in its failure branch but never sets a non-200 status code on the Response object it received as a parameter, so Starlette sends the default 200 regardless of which branch ran.
Expected behavior
GET /readyz returns HTTP 503 when the Redis ping fails, and 200 only when it succeeds. The response body already gets this right ({"status": "degraded", "reason": ...} vs {"status": "ready"}) -- only the status code is wrong.
Steps to reproduce
RateLimiter whose backing Redis is unreachable (a ping() that raises).GET /readyz.{"status": "degraded", "reason": "..."}. Expected: HTTP 503 with the same body.Why this matters
An HTTP status code is often the only thing a health-check consumer looks at -- load balancers, k8s probes, and uptime monitors frequently do not parse the body at all. A degraded state reported via a 200 is functionally the same as not reporting it, because readyz()'s failure branch never calls anything that sets the response status away from the default.
Suggested approach
Look at the two return paths in readyz(). One of them already knows it is in the failure case (it is building the "degraded" body) -- it just never tells Starlette what status code to send. Response objects passed into a handler as a parameter are mutable; look at how other status-code-less handlers in this codebase communicate a non-200 status back to Starlette while still returning a plain dict body.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_01_readyz_false_ready.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/readyz-false-readyFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/readyz-false-readySubmit 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:8080fast_echo/echo/.../large/{size_mb}slow_upstream/delay/{s}/flaky/always-downroutes.yamlcurl localhost:8080/healthzcurl localhost:8080/readyz/admin/*Authorization: Bearer <GATEWAY_ADMIN_TOKEN>You do not need Docker running at all to work the tickets — the test suite fakes Redis with fakeredis (plus lupa for real Lua-script support), so everything runs against a plain virtualenv:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/pytest practice_tickets/tests/test_ticket_09_breaker_threshold_off_by_one.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
./practice_tickets/run_tickets.sh 06 09 15 # just a few, by number
All 15 tests fail at the start; as you fix each ticket in app/, its test (and only its test) should flip to passing. .venv/bin/pytest tests/ runs the project's own main suite — as of writing it shows 8 pre-existing failures caused by ticket bugs 02, 04, 06, and 09; fixing those tickets fixes those failures too, not a separate task.
Level 1
Fix a bug
Read existing behaviour, correct it.