Nobody has started this yet — be first.
Business impact
API consumers depend on clean 4xx errors to build correct client-side validation and debug their own integration quickly. When a client mistake (setting both scheduling fields) surfaces as an opaque 500 instead of a clear "these two fields are mutually exclusive" 400, the client's developer wastes time assuming it's a chronos outage or a database problem, files a support ticket, and on-call has to dig through logs to find a Postgres constraint violation a one-line validation check would have caught at the door. It also pollutes error-rate dashboards and alerting with 500s that aren't actually server failures.
Problem
internal/api/jobs_handler.go's createJob validates that at least one of cron_expr/run_at is set, but no longer validates that they aren't both set. A request with both fields reaches s.jobs.Create, which reaches Postgres, which rejects the insert because of the jobs_schedule_check CHECK constraint. createJob's generic error handling for s.jobs.Create failures reports any error as a bare HTTP 500.
Current behavior
POST /v1/jobs with both cron_expr and run_at set returns HTTP 500 instead of 400.
Expected behavior
POST /v1/jobs with both cron_expr and run_at set should be rejected at the API layer with HTTP 400 and a message telling the caller the two fields are mutually exclusive -- before ever reaching the database.
Steps to reproduce
curl -i -X POST localhost:8080/v1/jobs
-H 'Content-Type: application/json'
-d '{"name":"x","cron_expr":"* * * * *","run_at":"2030-01-01T00:00:00Z","target_url":"http://example.com"}'
Why this matters
This is exactly the kind of validation that belongs at the API boundary, not the database: the constraint already exists at the DB layer as a correctness backstop, but relying on it alone means every violation surfaces as an undifferentiated 500 with no actionable message, at the cost of a round-trip to Postgres just to find out the request was malformed.
Suggested approach
createJob already has a check just above this one (req.CronExpr == nil && req.RunAt == nil, "exactly one ... is required"). Look for its sibling check on the opposite condition.
Acceptance criteria
Verification
TEST_DATABASE_URL="postgresql://chronos:chronos@localhost:5432/chronos?sslmode=disable" go test ./tickets/tests/... -run TestTicket05 -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/create-job-mutual-exclusive-validationFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/create-job-mutual-exclusive-validationSubmit 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.