Skip to content

fix(triggers): compute nextFireAt in workflow-trigger-service so schedule triggers fire - #568

Merged
jonwiggins merged 1 commit into
mainfrom
fix/trigger-service-next-fire-at
Aug 8, 2026
Merged

fix(triggers): compute nextFireAt in workflow-trigger-service so schedule triggers fire#568
jonwiggins merged 1 commit into
mainfrom
fix/trigger-service-next-fire-at

Conversation

@jonwiggins

Copy link
Copy Markdown
Owner

Bug

Schedule triggers created through workflow-trigger-service never fired.

There are two creation paths for workflow_triggers rows:

  • workflowService.createWorkflowTrigger / taskConfigService.createTaskConfigTrigger — used by the unified POST /api/tasks/:id/triggers route — compute nextFireAt from the cron expression at creation.
  • workflow-trigger-service.createTrigger — used by the legacy POST /api/jobs/:id/triggers route — inserted the row without nextFireAt.

The schedule poller (getDueScheduleTriggersAll) only selects rows with next_fire_at <= now, so a schedule trigger created via /api/jobs/:id/triggers was perpetually invisible to the poller and never fired. updateTrigger (behind PATCH /api/jobs/:id/triggers/:triggerId) had the same gap: changing the cron expression or re-enabling a schedule trigger never (re)computed nextFireAt, so a trigger disabled-then-re-enabled through that route also went dead.

Affected routes: POST /api/jobs/:id/triggers, PATCH /api/jobs/:id/triggers/:triggerId.
Unaffected: unified POST/PATCH /api/tasks/:id/triggers (standalone + repo-blueprint) and /api/task-configs/:id/triggers.

Fix

  • Extracted computeNextFire into apps/api/src/utils/cron.ts and reuse it from workflow-service.ts, task-config-service.ts, and workflow-trigger-service.ts — the first two previously each had their own private copy, so this de-dupes the cron parsing rather than adding a third copy.
  • workflow-trigger-service.createTrigger now stamps nextFireAt for enabled schedule triggers.
  • workflow-trigger-service.updateTrigger now recomputes nextFireAt on cron change / re-enable and clears it on disable, mirroring the other two services (it now also short-circuits to null for a missing id before touching the DB, same as updateTaskConfigTrigger).

Tests

  • apps/api/src/services/workflow-trigger.int.test.ts — the test that explicitly pinned the buggy behavior ("createTrigger does not compute nextFireAt … never fires") is flipped into a positive one: a schedule trigger created via the service gets a computed nextFireAt, becomes due, and the worker check fires it (run created, lastFiredAt stamped, nextFireAt advanced). Added an updateTrigger test covering recompute-on-re-enable, recompute-on-cron-change, and clear-on-disable.
  • apps/api/e2e/scheduled-trigger.e2e.test.ts — the legacy-route section pinned nextFireAt = null; it now asserts the legacy route computes nextFireAt like the unified route. The null-nextFireAt due-query case is preserved by nulling the column via direct SQL, since the API can no longer produce that state.
  • apps/api/src/services/workflow-trigger-service.test.ts — unit coverage for the new create/update nextFireAt behavior; existing update mocks adjusted for the new existence check.

Verification

  • cd apps/api && npx tsc --noEmit — clean
  • npx vitest run (apps/api unit) — 123 files, 2172 tests passed
  • npx vitest run --config vitest.integration.config.ts src/services/workflow-trigger.int.test.ts — 12/12 passed, twice
  • npx vitest run --config vitest.e2e.config.ts e2e/scheduled-trigger.e2e.test.ts — 4/4 passed, twice
  • pnpm format:check — clean

…dule triggers fire

Schedule triggers created via workflow-trigger-service (the service behind
POST/PATCH /api/jobs/:id/triggers) never fired: createTrigger inserted the
row without computing next_fire_at, and the schedule poller
(getDueScheduleTriggersAll) only selects rows with next_fire_at <= now, so
such triggers were perpetually invisible to it. updateTrigger had the same
gap — changing the cron or re-enabling a schedule trigger never rescheduled
it. The unified /api/tasks/:id/triggers route was unaffected (it goes
through workflowService / taskConfigService, which both computed the value).

Fix:
- Extract computeNextFire into src/utils/cron.ts and reuse it from
  workflow-service, task-config-service, and workflow-trigger-service
  (removing the two duplicated private copies).
- workflow-trigger-service.createTrigger stamps nextFireAt for enabled
  schedule triggers; updateTrigger recomputes it on cron change / re-enable
  and clears it on disable, mirroring the other two services.

Tests:
- workflow-trigger.int.test.ts: flipped the test that pinned the bug into a
  positive one (trigger created via the service becomes due and the worker
  check fires it), plus update-recompute coverage.
- scheduled-trigger.e2e.test.ts: the legacy-route section now asserts
  nextFireAt is computed; the null-nextFireAt due-query case is preserved
  via direct SQL since the API can no longer produce that state.
- workflow-trigger-service.test.ts: unit coverage for the new
  create/update nextFireAt behavior.
@jonwiggins
jonwiggins merged commit a467f6c into main Aug 8, 2026
10 checks passed
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.

1 participant