Nobody has started this yet — be first.
Business impact
Right now the only way to observe individual retry attempts from outside this package is to reimplement retry logic around it, or to infer attempt counts after the fact from a Result/Job's attempt history. Teams embedding pkg/retry in a different service (this package is explicitly designed to be go get-able standalone) routinely want per-attempt visibility -- custom metrics with their own labels, a structured log line their own logging pipeline expects, a trace span per attempt -- without forking the retry loop itself. Without a hook, every consumer either duplicates the loop or goes without that visibility.
Problem
Policy has no way to observe individual attempts as they happen; a caller only ever learns the final outcome (Do) or the full history after the fact (DoWithResult). A field, OnRetry func(attempt int, err error, delay time.Duration), has been added as scaffolding in pkg/retry/policy.go, but nothing calls it yet.
Current behavior
Setting Policy.OnRetry to a callback and running an operation that fails and gets retried never triggers the callback.
Expected behavior
DoWithResult calls policy.OnRetry(attempt, err, delay) -- when it's non-nil -- for every attempt that failed and is about to be retried (i.e. every attempt except the last one made, whether that last one succeeded, was non-retryable, or exhausted MaxAttempts), with delay being the backoff duration that's about to be slept before the next attempt.
Steps to reproduce
Configure a Policy with OnRetry set to a callback that records each call. Run retry.DoWithResult against an operation that fails twice then succeeds. Observe: OnRetry is never invoked, even though two attempts failed and were retried.
Why this matters
This is exactly the kind of small, additive extension point a general-purpose library needs so downstream consumers can integrate their own observability without becoming a contributor to (or a fork of) this package.
Suggested approach
DoWithResult's attempt loop in pkg/retry/do.go already computes delay right before sleeping it (via nextDelay), in the branch that runs only when the attempt failed, was retryable, and wasn't the last attempt. That's precisely the one place OnRetry needs to be invoked from -- guard for policy.OnRetry != nil first, since it's optional.
Acceptance criteria
Verification
go test ./test/ -run TestTask10 -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/onretry-hookFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/onretry-hookSubmit 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:8080POST /v1/chargesTo run against Redis (the multi-replica-safe backend) with zero local setup:
docker compose up --build # app + redis, hot-reloading on save
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
Level 2
Implement a feature
Extend the system within its own patterns.