Every artisan command the scheduler runs, in chronological order through a UTC day. php artisan schedule:list is the actual source of truth — most entries are declared directly in routes/console.php, but as of Phase 7 of the modularization roadmap, a module can contribute its own via a scheduledTasks() override on its service provider (clockwork:import-spinupwp and the three clockwork:pressable-*-report commands below now live in SpinupWpServiceProvider/PressableServiceProvider this way; routes/console.php calls ModuleRegistry::scheduleAll() once, at the end, to pull all of them in). Their listed times and behavior are unchanged by the move. The scheduler not running is a load-bearing failure mode — if any of these stop firing, Runbooks → Scheduler stuck is your starting point. That runbook covers cron itself dying; for whether a specific job is actually succeeding tick-to-tick (not just that cron is alive), see Scheduled Jobs Dashboard at /settings/scheduled-jobs.
Run the scheduler in foreground for development:
# On Linux, run directly:
php artisan schedule:work
# On macOS with Laravel Herd:
export PATH="$HOME/Library/Application Support/Herd/bin:$PATH"
php artisan schedule:work
The scheduler itself is watched by clockwork:scheduler-heartbeat (every minute, first): crontab cannot notice its own absence, so the web UI compares the last tick against a 5-minute threshold. The queue worker is monitored separately by clockwork:ensure-queue-worker (every 5 min), which kickstarts the worker if it has crashed or been throttled.
Every minute
| Command | What it does |
|---|---|
clockwork:scheduler-heartbeat |
Must stay first. Writes monitoring.scheduler_heartbeat_at. If that timestamp is older than 5 minutes, the next web request shows a layout banner, counts +1 on Issues, and fires scheduler_stale chat once. A never-ticked install is a yellow warning only (no chat, not an Issue). See Runbooks → Scheduler stuck. |
clockwork:process-server-updates |
Drain queued apt-update jobs, one server per tick. Long SSH sessions don’t stack. A live apt-get failure logs server_update_failed to action_logs and pings Mattermost/Slack (same event the reaper below fires for a stuck-and-reaped row). Success is not logged. |
clockwork:process-pending-bans |
Drain queued_for_ban → fail2ban-client banip over SSH. Small batches. |
clockwork:auto-approve-repeats |
Promote any IP with 2+ pending lockouts to the ban queue. Gated by auto_approve_repeats_enabled. |
Every 5 minutes
| Command | What it does |
|---|---|
clockwork:poll-servers |
Cloud-provider metrics (DigitalOcean + Hetzner) → server_metrics. Branches per row on servers.provider. Turns a server “red” and triggers AiRemedy spike triage on RED transition or high CPU. |
clockwork:watch-server-spikes |
Automated watchdog evaluating CPU, load average, and RAM spikes across all servers (including unlinked VPS via SSH probe); dispatches AiRemedy in Shadow Mode or Auto-Heal (contributed by AiRemedyServiceProvider). |
clockwork:tail-nginx-logs |
Pull new nginx log lines, inode-tracked to survive logrotate. |
clockwork:check-site-uptime |
HTTP probe each monitored site, transition state, fire Mattermost on transitions. The */N is configurable from /monitoring/settings (1/5/10/15). |
clockwork:ensure-queue-worker |
Watchdog for the com.clockwork.queue launchd service — checks for a live PID and kickstarts the worker if it has crashed. Recovery gap ≤ 5 min. If the kickstart itself fails, alerts Mattermost/Slack — every queued job is stuck at that point. |
Every 9 minutes
| Command | What it does |
|---|---|
clockwork:warm-weird-stats |
Pre-compute the /settings/weird-stats cache. Cold compute is ~30s on a 2M-row threat_logs window — past PHP-FPM’s max_execution_time. |
Every 15 minutes
| Command | What it does |
|---|---|
clockwork:pull-llar-lockouts |
Ingest active login lockouts from Gatekeeper (via Companion HMAC REST) with a direct-DB fallback to the native clockwork_lockouts table. Gated by IngestScheduleGate (per-source enable + window + cadence). |
clockwork:pull-wordfence-blocks |
Same for Wordfence blocks. |
clockwork:pull-site-metrics |
Per-site CPU/memory rollups from Companion sites advertising resource-sampler. Feeds /capacity. Pausable via monitoring.site_metrics_enabled. |
clockwork:ai-remedy-evaluate-outcomes |
Classifies 60-minute resolution outcomes (self_resolved, human_resolved, persisted, escalated, unknown) for past AiRemedy incident diagnoses by analyzing server_metrics, site_uptime_events, and human action_logs (contributed by AiRemedyServiceProvider). |
Hourly
| Command | What it does |
|---|---|
clockwork:rollup-traffic --backfill=2 |
Refresh today’s + yesterday’s per-site traffic rollups. Day-boundary catch-up is the --backfill=2. |
clockwork:reap-stale-update-jobs |
Flip Updates-page jobs stuck running >10 min to failed. Safety net for worker crashes. Now also pings Mattermost/Slack for any reaped nightly-batch row — reaped jobs bypass the normal live-failure alert path entirely. |
clockwork:reap-stale-server-updates |
Same for fleet system-update runs stuck past 2h (web47 once sat “running” for 4 weeks). Now also pings Mattermost/Slack (server_update_failed) for every reaped row — no nightly/manual distinction to gate on here, unlike the plugin-update reaper, so it always alerts. |
clockwork:ai-remedy-expire-unreviewed |
Marks unreviewed Interactive Copilot incidents older than 24 hours as expired so pending review queues remain fresh and actionable (contributed by AiRemedyServiceProvider). |
Daily — Bill.com window (01:00 UTC)
| Time | Command | What it does |
|---|---|---|
| 01:00 | clockwork:sync-bill-customers |
Bill.com customer list + invoice pull → link sites by domain. Gated on bill_com.enabled. |
| 01:30 | clockwork:sync-bill-care-plans |
Flip care_plan_enabled based on care-plan-Item invoicing. Respects care_plan_override. |
Daily — nightly auto-updates (ET)
| Time (ET) | Command | What it does |
|---|---|---|
| 01:30 | clockwork:refresh-companion-snapshot |
Pull /snapshot from each Companion-equipped site → cache to sites.companion_snapshot. (Was every-15-min, then 03:00 UTC — daily is enough; manual update batches trigger their own refresh.) Chained via ->then(): clockwork:run-nightly-plugin-updates fires immediately after this command’s process actually exits, not at a fixed clock offset — see the next row. |
| 01:45 | clockwork:ensure-companion-trust-proxy |
Idempotently inject CLOCKWORK_COMPANION_TRUST_PROXY into wp-config.php on care-plan auto-update sites so Companion’s rate limiter sees real IPs behind CF. Independently scheduled at a fixed time, not chained — unlike the update loop below, a stale snapshot here just means the constant gets injected a day late, not a silently dropped update. |
| 01:50 | clockwork:detect-stuck-companion-state |
Fleet-wide sweep for a failed install never retried (>24h) or an installed Companion gone silent (>3 days) — alerts on the transition into and out of “stuck” so unmonitored or failed installs are caught automatically. |
| chained | clockwork:run-nightly-plugin-updates |
Care-plan auto-update path — runs wp plugin update for each opted-in site. Per-site opt-out toggle on the site Updates tab. Not its own Schedule::command() entry — chained via ->then() off clockwork:refresh-companion-snapshot above (routes/console.php), ensuring the full snapshot refresh completes across the entire fleet before update queries evaluate candidates. |
Daily — security scan window (02:00 UTC)
| Time | Command | What it does |
|---|---|---|
| 02:00 | clockwork:scan-sitecheck |
Sucuri SiteCheck remote scan. Care-plan only. (Daily now, not weekly Mon — all care-plan scans match the once-a-day expectation.) |
| 02:15 | clockwork:check-blacklists |
URLhaus + Spamhaus DBL + optional Google Web Risk (legacy Safe Browsing v4 if the Web Risk key is empty). Hosting-tier (every site). |
| 02:30 | clockwork:verify-wp-core-checksums |
wp core verify-checksums per site — SSH for SpinupWP, Pressable’s async command API for Pressable. Care-plan only. |
| 02:45 | clockwork:run-companion-malware-scans |
In-WP malware probe (Companion plugin endpoint, SSH wp-cli fallback). Bypasses Cloudflare. Care-plan only. |
Daily — morning ingest + inventory (03:00 UTC)
| Time | Command | What it does |
|---|---|---|
| 03:00 | clockwork:sync-allowed-bots |
Refresh the Arcjet bot allowlist into allowed_bots. |
| 03:15 | clockwork:refresh-plugin-vulnerabilities |
Refresh the wpvulnerability.net CVE mirror for every installed plugin slug. Feeds the Issues page’s vulnerable-plugin flags. |
| 03:20 | clockwork:refresh-cisa-kev |
Sync the CISA Known Exploited Vulnerabilities catalog (JSON feed) into cisa_kev_entries for actively exploited CVE badging. |
| 03:30 | clockwork:import-spinupwp |
Idempotent server + site import. Refreshes cert dates. |
| hourly :32 | clockwork:import-turnkeywp |
Idempotent TurnkeyWP server + site import (TurnkeyWpServiceProvider). Runs after the SpinupWP import so a site released by SpinupWP is adopted before find-orphan-sites. |
| 03:35 | clockwork:find-orphan-sites |
Reclassify sites whose SpinupWP linkage was lost. |
| 03:45 | clockwork:reconcile-provider |
Link servers import-spinupwp couldn’t IP-cross-reference (manual adds, unknown-to-SpinupWP boxes). |
| 03:50 | clockwork:refresh-companion-snapshot --pending-updates-only |
Closes the SpinupWP/Companion snapshot gap: import-spinupwp (03:30) marks wp_plugin_updates=true for sites that have new updates, but the full snapshot refresh (01:30 ET) already ran using Companion’s pre-update cached state. This targeted re-pull refreshes only sites flagged pending so the Updates page sees fresh data without requiring a full fleet refresh. |
| 04:00 | clockwork:check-ssl-certs |
Per-site SSL state + Mattermost transitions. |
| 04:15 | clockwork:poll-system-updates |
SSH apt-check + reboot-required.pkgs + apt list --upgradable → server_update_snapshots. Runs against (a) SpinupWP-managed servers SpinupWP flagged with upgrade_required=true, and (b) every non-SpinupWP-managed server unconditionally, since nothing else sets that flag for GridPane/Hetzner/custom-VPS boxes. See the weekly --all sweep below, now a safety net rather than the primary mechanism for non-SpinupWP servers. |
| 04:30 | clockwork:prune-server-metrics |
Drop server_metrics rows older than 90 days. |
| 04:31 | clockwork:prune-scheduled-job-runs |
Drop scheduled_job_runs rows older than the retention window (default 30 days, CLOCKWORK_SCHEDULED_JOBS_RETENTION_DAYS). Keeps Settings → Scheduled Jobs bounded. |
| 04:32 | clockwork:prune-threat-logs |
Drop threat_logs older than the /settings/ingest window (default 30 days). DROP PARTITION on monthly MySQL partitions, else chunked DELETE. withoutOverlapping(240), runInBackground(). |
| 04:45 | clockwork:run-performance-scans --strategy=mobile --weekly-rotation |
Lighthouse run for tonight’s 1/7th of the care-plan fleet — GTmetrix primary, PSI fallback. Each site gets one scan per week. Scheduled at 04:45 to land just after GTmetrix’s daily credit refill (credits don’t bank). --strategy is display-only on GTmetrix’s tier; kept for row continuity. |
| 04:45 | clockwork:capture-site-screenshots |
Refresh each site’s homepage screenshot via Automattic’s mShots service, feeding the visual fleet grid view. withoutOverlapping(30), runInBackground(). |
| 04:45 | clockwork:detect-wp-plugins |
SSH wp-cli probe for active LLAR/Wordfence per WP site. |
| 04:50 | clockwork:detect-contact-forms |
Companion-aware contact-form detection. |
| 04:55 | clockwork:sync-companion-form-subscriptions |
Reconcile client-picked form-test subscriptions (Companion wp-admin Forms tab) into contact_form_tests. Slots between detect (04:50) and test (06:00). |
| 04:58 | clockwork:backup-relay-run (or clockwork:push-backup-relay-targets) |
Off-host archival to S3 Glacier IR. Per-site cadence (sites.backup_relay_frequency) is honoured in in-repo mode; custom/unhosted sites default to daily. --force / Backup Now skips the interval. External-agent mode still writes targets.json. See Features → Backup relay. |
| 05:00 | clockwork:check-cloudflare |
Per-site CF detection. (Promoted from weekly to daily — CF state changes too often to wait a week.) |
| 05:10 | clockwork:refresh-runtime-eol |
Pull PHP and WordPress release lifecycle tables from endoflife.date into app_settings for the Capacity dashboard and site widgets. |
Daily — Companion + form-tests (06:00 UTC)
| Time | Command | What it does |
|---|---|---|
| 06:00 | clockwork:test-contact-forms |
Run scheduled contact form tests per site. |
| 06:05 | clockwork:check-domain-expirations |
Poll ICANN RDAP registry APIs for domain expiration dates and registrar data. Weekly cadence for green sites, daily for yellow/red. |
| 06:15 (ET) | clockwork:nightly-update-summary |
Email summary of the previous night’s auto-update run. |
| 06:25 | clockwork:refresh-companion-capabilities |
Re-pull companion_capabilities per site. Diff-aware. |
| 06:30 | clockwork:push-companion-backups |
Fetch SpinupWP backup config + DO Spaces history → POST to each Companion site. |
| 06:32 | clockwork:pressable-backups-report |
Pressable counterpart — pulls real backup run history straight from Pressable’s /backups API (no separate inventory-import dependency, unlike the SpinupWP version). |
| 06:35 | clockwork:push-companion-traffic |
Push yesterday’s traffic rollup to each Companion-equipped site. |
| 06:37 | clockwork:pressable-traffic-report |
Pressable counterpart — pulls page-view period totals straight from Pressable’s stats API (no nginx access-log rollup dependency). |
| 06:39 | clockwork:pressable-security-summary-report |
Pressable-only: known plugin/theme vulnerabilities + Defensive Mode status. No SpinupWP equivalent — pure additional capability, not a parity fix. |
| 06:40 | clockwork:push-gatekeeper-settings |
Daily catch-up sync for Gatekeeper login lockout settings. Pushes policy defaults and site overrides to Companion and Renegade across the fleet. |
| 06:40 | clockwork:pull-backup-relay-report |
External-agent mode only: reads the backup-relay droplet’s last run summary back from S3 and records it into backup_relay_runs. Also alerts if no run has completed within 6 days. See Features → Backup relay. |
| 06:45 | clockwork:push-update-exceptions |
Daily catch-up sync for update exceptions (paused auto-updates). Pushes active exceptions to Companion and Renegade. |
| 07:15 | clockwork:check-robots-txt |
Check root /robots.txt across monitored sites to detect accidental crawler disallow directives. |
| 09:00 | clockwork:feedback-prompt |
Compile approved feedback into a master Claude implementation prompt at storage/app/prompts/latest-feedback-prompt.md, via FeedbackServiceProvider::scheduledTasks(). |
Weekly
| Day / time | Command | What it does |
|---|---|---|
| Sun 05:15 | clockwork:cleanup-spam-comments |
Purge stale spam and trash comments across Companion-equipped sites, via CommentModerationServiceProvider::scheduledTasks(). |
| Sun 05:30 | clockwork:collect-field-metrics |
Query Google Chrome UX Report (CrUX) API for 28-day rolling real-user Core Web Vitals (LCP, INP, CLS, FCP, TTFB) for care-plan sites. |
| Sun 05:30 | clockwork:refresh-fail2ban-ignoreip |
Refresh CF ranges + fleet IPs in every server’s jail. |
| Sun 05:30 | clockwork:scan-wp7-truncation --repair |
Sweep + auto-repair WP 7.0 upgrades that left wp-includes/php-ai-client/ files with truncated names. |
| Sun 05:45 | clockwork:refresh-cloudflare-real-ip |
Push the nginx CF-Connecting-IP snippet and its sites-enabled bridge — the conf.d file alone is inert on SpinupWP boxes. See Integrations → Cloudflare. |
| Mon 03:30 | clockwork:refresh-closed-plugins |
Check WordPress.org plugin directory status for closed/zombieware plugins across unique fleet slugs. Surfaced on /issues. |
| Mon 04:00 | clockwork:check-email-auth |
Inspect SPF 10-lookup limits, loop detection, DMARC policies, and DKIM selector probing via DNS-over-HTTPS across unique client apex domains, via EmailAuthServiceProvider::scheduledTasks(). |
| Mon 04:30 | clockwork:poll-system-updates --all |
Full-fleet apt-update sweep, bypassing the daily job’s upgrade_required gate entirely. Now a safety net for SpinupWP servers whose mirrored flag is stale or wrong — the daily job above already polls non-SpinupWP-managed servers unconditionally, so this sweep is no longer their only path to being polled. |
| Mon 06:00 | clockwork:composer-audit |
Composer dependency vulnerability scan. |
| Mon 06:15 | clockwork:send-telemetry |
Anonymous usage report to the project maintainer. On by default (CLOCKWORK_TELEMETRY_ENABLED=true); disable in Settings → Maintenance or via env. See Reference → env vars. |
| Mon 06:30 | clockwork:security-check --ssh --quiet-ok |
System security audit. |
Monthly
(none currently scheduled)
Deliberately not scheduled
clockwork:import-pressable and clockwork:install-companion-pressable are manual-only today — Pressable site inventory doesn’t churn enough yet to need a daily re-import, and Companion rollout to new Pressable sites is still a deliberate per-site/batch step, not a fleet-wide cron. See Integrations → Pressable.
