Nobody has started this yet — be first.
Business impact
This bug inverts the entire purpose of the reaper. The reaper exists to recover work only after a worker has genuinely disappeared (crashed, network partition) -- it's the safety net for the rare case. As shipped, it does the opposite: it targets leases that are still comfortably within their validity window (runs a worker is right now actively and legitimately executing) and leaves truly abandoned/expired leases untouched forever. That means every currently-in-progress run is at constant risk of being handed to a second worker while the first is still processing it -- guaranteed duplicate execution on a much larger scale than the README's documented at-least-once tradeoff ever intended -- and a run whose worker genuinely crashed is never recovered at all.
Problem
pkg/queue/queue.go's ReclaimExpiredLeases selects rows to requeue with WHERE status IN ('leased', 'running') AND leased_until > now(). leased_until > now() means "the lease has NOT YET expired" -- the opposite of what a reaper reclaiming expired leases should target. The comparison operator is inverted; it should be leased_until < now().
Current behavior
A run with leased_until in the past keeps status "leased" after ReclaimExpiredLeases instead of becoming "pending", while a run with leased_until in the future gets reclaimed incorrectly.
Expected behavior
ReclaimExpiredLeases should only requeue runs whose leased_until has already passed. Runs with an active, not-yet-expired lease must be left completely alone.
Steps to reproduce
Why this matters
A flipped comparison operator on a time boundary is one of the most dangerous classes of bug precisely because both directions compile, both look superficially reasonable, and the bug only manifests under concurrent load in ways that are easy to attribute to something else ("why did this webhook fire twice?") rather than trace back to the reaper.
Suggested approach
Think about what leased_until represents (the moment a lease stops being valid) and which side of "now" a lease needs to be on for it to be eligible for reclaim.
Acceptance criteria
Verification
TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable" go test ./tickets/tests/... -run TestTicket12 -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/reclaim-inverted-comparisonFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/reclaim-inverted-comparisonSubmit 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.