---
phase: quick-260610-i4x
plan: '01'
subsystem: broker
tags: [scheduler, setInterval, node-cron, fix]
dependency_graph:
  requires: []
  provides: [reliable-broker-scheduling]
  affects: [broker/poller, broker/outboxWorker, broker/reminderScheduler]
tech_stack:
  added: []
  patterns: [setInterval-based-scheduling]
key_files:
  created: []
  modified:
    - apps/api/src/broker/poller.ts
    - apps/api/src/broker/outboxWorker.ts
    - apps/api/src/broker/reminderScheduler.ts
decisions:
  - 'Use setInterval for broker worker scheduling: node-cron 4.2.1 silently skipped executions in the long-running API process; setInterval is reliable in the same process'
metrics:
  completed: '2026-06-10'
---

# Quick Task 260610-i4x: Replace node-cron with setInterval in Broker Workers Summary

**One-liner:** Replaced `node-cron` `schedule()` calls with `setInterval` (300 s / 15 s / 60 s) in all three broker worker `start*` functions so scheduled tasks actually fire in the long-running API process.

## Tasks Completed

| Task | Name                                                                            | Commit  | Files                                            |
| ---- | ------------------------------------------------------------------------------- | ------- | ------------------------------------------------ |
| 1    | Swap node-cron schedule() for setInterval in the three worker start\* functions | d9efbc1 | poller.ts, outboxWorker.ts, reminderScheduler.ts |

## Changes Made

### apps/api/src/broker/poller.ts

- Removed `import { schedule } from 'node-cron'`
- `startBrokerPoller`: `schedule('*/5 * * * *', cb)` → `setInterval(cb, 5 * 60 * 1000)`
- Updated file-header and JSDoc comments to reference setInterval and document the WHY

### apps/api/src/broker/outboxWorker.ts

- Removed `import { schedule } from 'node-cron'`
- `startOutboxWorker`: `schedule('*/15 * * * * *', cb)` → `setInterval(cb, 15 * 1000)`
- Updated header comment and scheduler JSDoc

### apps/api/src/broker/reminderScheduler.ts

- Removed `import { schedule } from 'node-cron'`
- `startReminderScheduler`: `schedule('* * * * *', cb)` → `setInterval(cb, 60 * 1000)`
- Updated file-header "Fires every minute" line and scheduler JSDoc

Callback bodies (the `.catch`-wrapped `runPoll` / `runOutboxDrain` / `runReminderCheck` calls) are byte-for-byte unchanged. No `.unref()` added — intervals intentionally keep the event loop alive.

## Verification Results

- `! grep -rn "import.*node-cron" apps/api/src/broker/`: exits 1 (no matches) — all imports removed
- `pnpm --filter @familysync/api typecheck`: **exit 0** — clean
- `pnpm --filter @familysync/api exec vitest run tests/broker/`: **8 test files passed, 91 tests passed**

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

None.

## Threat Flags

None — no new network surface, auth paths, file access, or schema changes introduced.

## Self-Check: PASSED

- [x] `apps/api/src/broker/poller.ts` modified — confirmed
- [x] `apps/api/src/broker/outboxWorker.ts` modified — confirmed
- [x] `apps/api/src/broker/reminderScheduler.ts` modified — confirmed
- [x] Commit d9efbc1 exists on branch gsd/v1.0-milestone
- [x] No `import { schedule } from 'node-cron'` in any of the three files
- [x] typecheck: exit 0
- [x] broker tests: 8 files / 91 tests — all green
