Skip to content

fix(scheduler): never persist 0001-01-01 as next_run_at (Closes #1)#8

Merged
hacka0wi merged 1 commit into
mainfrom
fix/scheduler-next-run-zero
May 6, 2026
Merged

fix(scheduler): never persist 0001-01-01 as next_run_at (Closes #1)#8
hacka0wi merged 1 commit into
mainfrom
fix/scheduler-next-run-zero

Conversation

@hacka0wi

@hacka0wi hacka0wi commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

Engine was writing next_run_at = 0001-01-01 00:00:00 to the DB after
registering a cron job with gocron — job.NextRun() returns a zero
time.Time before the first firing, and we passed that straight to
UPDATE schedulers SET next_run_at = $1. The UI then rendered "Overdue"
for every freshly-seeded scheduler.

Fix: fall back to parsing the cron ourselves (robfig/cron, already a
dep) when gocron returns zero, applying the scheduler's timezone. Skip
the DB write entirely if both sources fail.

Closes #1.

Changes

  • internal/scheduler/manager.go
    • computeNextRun(schedule, timezone) helper using robfig/cron
    • resolveNextRun(s, job) — gocron first, hand-computed fallback, zero last
    • AddJob and the post-firing update path use the new helper and skip the write on zero
  • internal/scheduler/manager_test.go — 4 new tests for the fallback (hourly cadence, Asia/Bangkok timezone, bad-tz fallback to UTC, bad cron error)

Test plan

  • go test ./internal/scheduler/... passes (existing + 4 new tests)
  • go vet ./... clean
  • go build ./... clean
  • Manual: seed demo data, restart engine, observe next_run_at is a real future timestamp (not 0001-01-01)
  • Manual: open /p/<id>/schedulers — every active row shows "next In Xm/Xh/Xd" instead of "Overdue"

🤖 Generated with Claude Code

After AddJob registers a cron task with gocron, calling job.NextRun()
immediately can return a zero time.Time — the job hasn't fired yet, so
gocron has no scheduled run to report. The engine was writing that zero
straight to schedulers.next_run_at, which Postgres stores as
0001-01-01 00:00:00. The web UI compares next_run_at to Date.now() and
renders "Overdue" for every freshly-seeded scheduler, even though the
cron expression is valid and the engine will fire it on time.

Add a fallback path: when gocron returns zero, parse the cron expression
ourselves with robfig/cron (already a dependency) and compute the next
firing in the scheduler's configured timezone. Skip the DB write if both
sources fail to produce a real instant — better to leave the column
untouched than to write 0001-01-01.

The same pattern applies after a fire completes; updated both call sites
through a new resolveNextRun helper so they stay in sync.

Tests cover the fallback computation directly: hourly cadence advances,
Asia/Bangkok timezone is honored, an unrecognised tz falls back to UTC,
and bogus cron strings return a parse error.
@hacka0wi hacka0wi merged commit 802235d into main May 6, 2026
8 checks passed
@hacka0wi hacka0wi deleted the fix/scheduler-next-run-zero branch May 6, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Engine writes next_run_at = 0001-01-01 on scheduler load (UI shows 'Overdue')

1 participant