Nobody has started this yet — be first.
Business impact
Every chronos replica applies migrations on startup before serving traffic. If Migrate() crashes on any restart after the very first one, the service can never come back up -- not after a routine redeploy, not after a crash, not after a node reschedule in Kubernetes. This is a cannot-start-at-all bug: the first deploy works, and every deploy after that is an outage. On-call would see every replica crash-looping against a healthy database, a uniquely confusing failure mode at 3am because the database is fine and the migrations are fine -- the code just refuses to believe it.
Problem
db.Migrate (in internal/db/db.go) calls golang-migrate's m.Up() and treats any non-nil error as fatal: if err := m.Up(); err != nil { return fmt.Errorf("apply migrations: %w", err) }. m.Up() returns migrate.ErrNoChange when the schema is already at the latest version -- the expected, successful outcome on every restart after the first. Treating it as fatal means the second, third, and every subsequent startup errors out immediately.
Current behavior
A second call to db.Migrate against an already-migrated database returns an error, "apply migrations: no change", instead of succeeding silently.
Expected behavior
Migrate() should return successfully (no error) both when it actually applies new migrations and when there are none pending to apply. Only a genuine migration failure (bad SQL, connectivity failure, version mismatch, etc.) should be treated as fatal.
Steps to reproduce
go run ./cmd/chronos, or a second container start in the Docker stack).Why this matters
golang-migrate uses ErrNoChange as a sentinel for "already up to date" rather than a special return value, precisely so callers can distinguish it from a real failure with errors.Is. Skipping that check conflates "nothing to do" with "something went wrong."
Suggested approach
Look at how Migrate() currently decides whether m.Up()'s error is fatal. There is a well-known sentinel error (migrate.ErrNoChange) exported by the github.com/golang-migrate/migrate/v4 package specifically for this situation, and the standard library's errors package has a function for checking whether an error is (or wraps) a specific sentinel.
Acceptance criteria
Verification
TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable" go test ./tickets/tests/... -run TestTicket01 -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/migrate-errnochangeFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/migrate-errnochangeSubmit 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.