Nobody has started this yet — be first.
Business impact
max_retries is a contract: a job owner sets it expecting exactly that many attempts before chronos gives up and dead-letters the run for them to investigate. Silently granting one extra attempt beyond what was configured breaks that contract for every single job in the system, not just an edge case. For a job whose target_url triggers a paid API call, sends a customer notification, or charges a card, "one extra attempt beyond what I configured" is an unauthorized action the job owner explicitly tried to prevent by setting max_retries in the first place.
Problem
pkg/queue/queue.go's MarkRetry decides whether to dead-letter with if attemptCount > maxRetries. attemptCount here is the attempt that just failed. With max_retries = 2, the call sequence is: attempt 1 fails (attemptCount=1, requeue), attempt 2 fails (attemptCount=2, 2 > 2 is false, requeue again), attempt 3 fails (attemptCount=3, finally dead-letter). That's 3 attempts for a job configured for 2.
Current behavior
After MarkRetry(runID, attemptCount=2, maxRetries=2), the run status is "pending" instead of "dead".
Expected behavior
A job configured with max_retries = N should get exactly N attempts total. The Nth failed attempt should dead-letter the run, not requeue it for an N+1th attempt.
Steps to reproduce
Why this matters
This is a boundary condition on when "at the limit" becomes "over the limit" -- > vs >= -- the single most common flavor of off-by-one bug, and one that's easy to miss in review because both read as plausible at a glance.
Suggested approach
Think through the exact sequence of attemptCount values MarkRetry sees across a job's failed attempts, and at which value the Nth (final) attempt should stop being retried and start being dead-lettered.
Acceptance criteria
Verification
TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable" go test ./tickets/tests/... -run TestTicket08 -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/markretry-offbyoneFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/markretry-offbyoneSubmit 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:8081app-1http://localhost:8082app-2http://localhost:8090curl localhost:8081/healthzcurl localhost:8081/readyzWork the tickets in tickets/docs/ (ordered easy → hard); each names one Go test in tickets/tests/. Most tickets need a real Postgres (tickets 4, 11, and 18 are pure-function tests and do not):
export TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable"
go test ./tickets/tests/... -run TestTicket08 -v # a single ticket
./tickets/run_tickets.sh # all 20, clean pass/fail summary
Use -race when working on tickets 15, 16, or 17 (the concurrency ones) -- run_tickets.sh already does this for every ticket. Tickets 15 and 16 are adjacent bugs in the same function (pkg/leader/leader.go's lock-release path); fix ticket 16 first, since ticket 15's test keeps failing for ticket 16's reason until that one is fixed too.
Level 1
Fix a bug
Read existing behaviour, correct it.