Summary
Investigated switching bin/cron-service and bin/browser-service from launchd/systemd management to plain crontab. Conclusion: keep the current approach — crontab would be a regression, not an improvement.
Current setup
bin/cron-service runs cron/server.py, a FastAPI/uvicorn process kept alive by launchd (macOS) / systemd (Linux). Internally it runs routine_scheduler.scheduler_loop, which ticks every 30s and drives per-prospect sweeps on a ~60s cadence, using in-process asyncio.Locks to prevent overlapping runs, plus a live /health endpoint the dashboard polls.
bin/browser-service keeps a persistent Chrome process open with a logged-in CDP session (LinkedIn profile), managed the same way.
Why crontab doesn't fit
- Browser needs a persistent process, not a periodic job. Crontab only fires commands at intervals; it can't hold a long-lived Chrome/CDP session open. Emulating this would mean a
* * * * * "check if it died, restart it" job — reinventing launchd's KeepAlive / systemd's Restart=on-failure, worse.
- Sub-minute scheduling granularity. The routine scheduler ticks every 30s and sweeps every ~60s; crontab's floor is 1 minute.
- Shared in-process locking. Overlap prevention across sweep kinds currently uses in-memory
asyncio.Locks in one long-running process. Under crontab (fresh process per invocation) this would require file-based locking (flock) instead — extra moving parts for no benefit.
- Live health endpoint. The dashboard/
install.sh rely on an always-up /health HTTP endpoint. Crontab has no persistent process to serve this, so we'd still need a long-running service — putting us back to needing a service manager anyway.
- No catch-up after sleep/reboot. macOS has no anacron; missed cron minutes are just skipped. launchd/systemd handle start-on-boot and crash-restart natively. Apple's own docs steer users toward launchd over cron.
Routine state (last_run_at, backoff) is already persisted to disk (dashboard_routines.json), so that part is fine under either model — the blockers are specifically the persistent browser process and sub-minute in-process locking/health-check.
Decision
No code change. Keep launchd (macOS) / systemd (Linux) as the service backend for both cron-service and browser-service.
Summary
Investigated switching
bin/cron-serviceandbin/browser-servicefrom launchd/systemd management to plain crontab. Conclusion: keep the current approach — crontab would be a regression, not an improvement.Current setup
bin/cron-servicerunscron/server.py, a FastAPI/uvicorn process kept alive by launchd (macOS) / systemd (Linux). Internally it runsroutine_scheduler.scheduler_loop, which ticks every 30s and drives per-prospect sweeps on a ~60s cadence, using in-processasyncio.Locks to prevent overlapping runs, plus a live/healthendpoint the dashboard polls.bin/browser-servicekeeps a persistent Chrome process open with a logged-in CDP session (LinkedIn profile), managed the same way.Why crontab doesn't fit
* * * * *"check if it died, restart it" job — reinventing launchd'sKeepAlive/ systemd'sRestart=on-failure, worse.asyncio.Locks in one long-running process. Under crontab (fresh process per invocation) this would require file-based locking (flock) instead — extra moving parts for no benefit.install.shrely on an always-up/healthHTTP endpoint. Crontab has no persistent process to serve this, so we'd still need a long-running service — putting us back to needing a service manager anyway.Routine state (
last_run_at, backoff) is already persisted to disk (dashboard_routines.json), so that part is fine under either model — the blockers are specifically the persistent browser process and sub-minute in-process locking/health-check.Decision
No code change. Keep launchd (macOS) / systemd (Linux) as the service backend for both
cron-serviceandbrowser-service.