Nobody has started this yet — be first.
Business impact
Right now, a newly materialized job_run waits out the worker's IDLE_POLL_INTERVAL (default 3 seconds) before any instance notices it if all workers happened to just poll and find nothing. For most jobs this delay is invisible, but for POST /v1/jobs/{id}/trigger -- explicitly documented as the way to fire an immediate one-off run so a demo doesn't require waiting on a real cron interval -- a multi-second delay before it actually starts undercuts the whole point of the endpoint. At larger scale, shortening IDLE_POLL_INTERVAL to compensate means every idle instance hits the database far more often for no reason, trading latency for load in the wrong direction.
Problem
pkg/notify/notify.go exists with a single placeholder function, Listen, that panics with "TODO(ticket-E6): Listen not implemented". Nothing in pkg/worker or pkg/queue calls it yet.
Current behavior
Calling notify.Listen returns an error (a recovered panic: "TODO(ticket-E6): Listen not implemented") instead of a working notification channel.
Expected behavior
notify.Listen(ctx, pool, channel) subscribes to Postgres NOTIFY messages on channel and delivers one value on the returned channel per notification received, until ctx is cancelled.
Steps to reproduce
Why this matters
This is the hardest ticket in the set because LISTEN shares an unusual property with the advisory lock in pkg/leader: it is session-scoped. A LISTEN issued on one connection is meaningless if that connection gets returned to a pool and reused for something else -- the subscription lives and dies with that one specific backend connection, exactly like pg_try_advisory_lock. Implementing this correctly requires checking out a dedicated connection (not the shared query pool) and keeping it alive for as long as the caller wants to keep listening, then cleanly releasing it when ctx is cancelled.
Suggested approach
Read pkg/leader/leader.go's Elector for the "dedicated connection checked out of the pool, held for the lifetime of the thing, released on context cancellation" pattern -- the shape is very similar here. For the actual wait loop, pgx's connection type exposes a method for blocking until a notification arrives (or the context is cancelled) on a LISTEN-ed connection; look for it on the type you get back from checking out a raw connection (as opposed to a pooled one) -- that method already respects context cancellation internally.
Acceptance criteria
Verification
TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable" go test ./tickets/tests/... -run TestTicket20 -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/listen-notifyFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/listen-notifySubmit 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 2
Implement a feature
Extend the system within its own patterns.