Nobody has started this yet — be first.
Business impact
MaxRetries is a deployment-tunable knob (via WEATHER_MAX_RETRIES) that operators use to decide how hard to fight for a successful upstream response before giving up and falling back to stale/error. Right now the client silently gives up one attempt earlier than configured -- with MaxRetries: 2 (documented as "1 initial + 2 retries = 3 total attempts"), it only ever makes 2 attempts. In production this quietly makes the service less resilient to transient upstream hiccups than its own configuration promises, with nothing in the logs or metrics to reveal the discrepancy.
Problem
The retry loop's boundary condition is off by one -- it stops one iteration earlier than MaxRetries calls for, so a transient failure that would have succeeded on the final configured retry attempt now exhausts retries and fails instead.
Current behavior
With MaxRetries: 2 against an upstream that fails its first two requests and succeeds on the third, FetchCurrent returns ErrUpstreamUnavailable after only 2 attempts, never making the third, successful one. With MaxRetries: 0 the effect is worse: the loop body never runs at all, so zero attempts are made.
Expected behavior
With MaxRetries: N, the client makes exactly N + 1 total attempts (1 initial attempt + N retries) before giving up, as the MaxRetries doc comment on ClientConfig already describes.
Steps to reproduce
Configure MaxRetries: 2 against a server that fails its first two requests (transient 503) and succeeds on the third. client.FetchCurrent(ctx, "Berlin")
Why this matters
A loop boundary (<= vs. <) is one character, compiles either way, and only actually diverges in behavior on the last allowed attempt -- which is exactly the attempt most likely to be exercised in a real "upstream recovers just in time" scenario, and the least likely to be caught by a quick manual check ("it retried, looks fine").
Suggested approach
Look at the loop bounds in Client.FetchCurrent (for attempt := 0; attempt <op> c.cfg.MaxRetries; attempt++) and work out, for MaxRetries = 2, exactly which values of attempt the loop body actually runs for versus how many total attempts that represents. Also walk through MaxRetries = 0 by hand -- the same off-by-one has a second, worse consequence there.
Acceptance criteria
Verification
go test ./practicetickets/... -run TestTicket02 -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
$git checkout -b fix/retry-off-by-oneFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/retry-off-by-oneSubmit 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 inweather_dashboarddbappmockweatherWEATHER_API_BASE_URL=https://api.openweathermap.orgWEATHER_API_KEYdocker-compose.prod.ymlRun the project's own test suite:
make test # go test ./...
make test-race # go test -race ./... (includes the concurrency/partial-failure test)
Work the tickets in PRACTICE_TICKETS.md (TICKET-01 through TICKET-10); each names one Go test in practicetickets/:
./practice_tickets_run.sh # all 10, pass/fail summary
go test ./practicetickets/... -run TestTicket01 -v # a single ticket
go test -race ./practicetickets/... -run TestTicket10 -v # ticket 10 needs -race to observe its bug
Tickets 03, 04, 05, and 06 touch the saved-cities Postgres store and need a reachable test database, set via WD_TEST_DATABASE_URL (defaults to postgres://postgres:postgres@localhost:5436/weather_dashboard_test if unset). Tickets 01, 02, 07, 08, 09, and 10 need no database at all.
Level 1
Fix a bug
Read existing behaviour, correct it.