{"id":371559,"date":"2026-09-28T09:10:24","date_gmt":"2026-09-28T09:10:24","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/cron-monitor\/"},"modified":"2026-09-28T14:20:00","modified_gmt":"2026-09-28T14:20:00","slug":"mcron-monitor","status":"publish","type":"plugin","link":"https:\/\/ca.wordpress.org\/plugins\/mcron-monitor\/","author":23547000,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.5.6","stable_tag":"1.5.6","tested":"7.1.2","requires":"6.5","requires_php":"8.1","requires_plugins":null,"header_name":"Cron Monitor","header_author":"Magnus V.","header_description":"See what your WP-Cron jobs actually did: run history, error logs, timing, duplicates and which plugin owns each event.","assets_banners_color":"2d266e","last_updated":"2026-09-28 14:20:00","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/wordpress.org\/plugins\/mcron-monitor\/","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":39,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.5.5":{"tag":"1.5.5","author":"macvej","date":"2026-09-28 09:07:23","revision":3716721},"1.5.6":{"tag":"1.5.6","author":"macvej","date":"2026-09-28 14:20:00","revision":3717349}},"upgrade_notice":{"1.5.6":"<p>Safe upgrade, no database change. The plugin is renamed to Cron Monitor; settings and history carry\nover untouched.<\/p>","1.5.5":"<p>Safe upgrade, no database change. Hardens how the admin screens validate and escape event\nreferences.<\/p>","1.5.4":"<p>Safe upgrade, no database change. Fixes several edge cases in the admin screens, including hook\nnames with unusual characters and impossible Run History date filters.<\/p>","1.5.3":"<p>Updates the database schema (version 6) on first load. Addresses the wordpress.org review: all\nqueries fully prepared, input sanitized field by field, and WordPress 6.5 supported again. Also\nfixes lost or duplicated alerts, duplicate removal, and concurrent settings saves.<\/p>","1.5.2":"<p>No schema change (a one-time version bump). Meets a wordpress.org review requirement: the recorder\nno longer reads PHP&#039;s error level. Errors silenced with @ are now counted; notices and deprecations\nare recorded only when WP_DEBUG is on. Network-activated sites now prune every subsite.<\/p>","1.5.1":"<p>Safe upgrade, no database change. Fixes a fatal error on three screens on hosts without the\nmbstring extension, stops a pinned event silently ceasing to recur, and removes a phishing surface\nin admin notices.<\/p>","1.5.0":"<p>Safe upgrade. No database change and no behaviour change: the two new alert-channel switches are\nboth on, so a site that already had a webhook URL or an alert address saved keeps delivering to it.<\/p>","1.4.0":"<p>Safe upgrade. No database change, no settings change and no behaviour change \u2014 this release only\nreduces how much of the plugin loads on requests that are not using it.<\/p>","1.3.0":"<p>Adds a switch that turns run recording off entirely, for sites that want the schedule tools without\nthe per-run cost. Recording stays on after upgrading; nothing is reset and no rows are removed.<\/p>","1.2.0":"<p>Adds a stopped-running alert, email alerts and a REST API. No settings are reset; the new email\nfield is empty until you fill it in.<\/p>","1.1.0":"<p>Adds a hook pauser, an event editor, custom schedules, CSV export and cron-spawn diagnosis. No\nsettings are reset; the schema upgrades in place.<\/p>","1.0.0":"<p>First release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3716721,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3716721,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3716721,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3716721,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3716721,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.5.5","1.5.6"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3716721,"resolution":"1","location":"assets","locale":"","width":3801,"height":1837},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3716721,"resolution":"2","location":"assets","locale":"","width":3804,"height":1804},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3716721,"resolution":"3","location":"assets","locale":"","width":3799,"height":1788},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3716721,"resolution":"4","location":"assets","locale":"","width":3802,"height":1814},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3716721,"resolution":"5","location":"assets","locale":"","width":3792,"height":1795}},"screenshots":{"1":"Scheduled Events: every WP-Cron event with its owner, last run and recent-run sparkline.","2":"Run History: each run's status, duration, memory, queries and warnings.","3":"Cron Health: what to fix first, who uses the cron budget, and whether wp-cron.php is reachable.","4":"Schedules: every registered interval and the events using it, plus custom schedules.","5":"Settings: retention, email and webhook alerts, and Action Scheduler recording."}},"plugin_section":[],"plugin_tags":[4567,4688,264567,282730,4568],"plugin_category":[59],"plugin_contributors":[277115],"plugin_business_model":[],"class_list":["post-371559","plugin","type-plugin","status-publish","hentry","plugin_tags-cron","plugin_tags-cron-job","plugin_tags-cron-log","plugin_tags-scheduled-events","plugin_tags-wp-cron","plugin_category-utilities-and-tools","plugin_contributors-macvej","plugin_committers-macvej"],"banners":{"banner":"https:\/\/ps.w.org\/mcron-monitor\/assets\/banner-772x250.png?rev=3716721","banner_2x":"https:\/\/ps.w.org\/mcron-monitor\/assets\/banner-1544x500.png?rev=3716721","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/mcron-monitor\/assets\/icon.svg?rev=3716721","icon":"https:\/\/ps.w.org\/mcron-monitor\/assets\/icon.svg?rev=3716721","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/mcron-monitor\/assets\/screenshot-1.png?rev=3716721","caption":"Scheduled Events: every WP-Cron event with its owner, last run and recent-run sparkline."},{"src":"https:\/\/ps.w.org\/mcron-monitor\/assets\/screenshot-2.png?rev=3716721","caption":"Run History: each run's status, duration, memory, queries and warnings."},{"src":"https:\/\/ps.w.org\/mcron-monitor\/assets\/screenshot-3.png?rev=3716721","caption":"Cron Health: what to fix first, who uses the cron budget, and whether wp-cron.php is reachable."},{"src":"https:\/\/ps.w.org\/mcron-monitor\/assets\/screenshot-4.png?rev=3716721","caption":"Schedules: every registered interval and the events using it, plus custom schedules."},{"src":"https:\/\/ps.w.org\/mcron-monitor\/assets\/screenshot-5.png?rev=3716721","caption":"Settings: retention, email and webhook alerts, and Action Scheduler recording."}],"raw_content":"<!--section=description-->\n<p>WordPress runs background tasks, called cron jobs, for things like publishing scheduled posts,\nsending emails and running backups. It keeps no record of them, so when one fails or stops you\nnever find out.<\/p>\n\n<p>Cron Monitor keeps that record. It shows what ran, what failed and which plugin is behind each\njob, and it tells you when something goes wrong.<\/p>\n\n<h4>Features<\/h4>\n\n<ul>\n<li><strong>Run history<\/strong>: every job that ran, how long it took and whether it worked.<\/li>\n<li><strong>Catches crashes<\/strong>: fatal errors and timeouts are recorded too.<\/li>\n<li><strong>Plugin owner<\/strong>: see which plugin or theme created each job.<\/li>\n<li><strong>Safe cleanup<\/strong>: finds jobs left behind by deleted plugins and tells you which are safe to delete.<\/li>\n<li><strong>Duplicate finder<\/strong>: spots jobs scheduled twice and removes the extras, with a preview first.<\/li>\n<li><strong>Health check<\/strong>: a list of cron problems on your site, most important first.<\/li>\n<li><strong>Alerts<\/strong>: email, Slack or Discord when a job crashes, runs late or stops running.<\/li>\n<li><strong>Manage jobs<\/strong>: add, edit, run now, pause or delete any scheduled job.<\/li>\n<li><strong>Pause a job<\/strong>: stop one job without turning off the plugin that owns it.<\/li>\n<li><strong>Custom schedules<\/strong>: create your own intervals, like every 10 minutes.<\/li>\n<li><strong>Fixed time of day<\/strong>: keep a daily job at 03:00, even when the clocks change.<\/li>\n<li><strong>What changed<\/strong>: see which jobs appeared or disappeared in the last week.<\/li>\n<li><strong>WooCommerce<\/strong>: logs failed Action Scheduler tasks.<\/li>\n<li><strong>CSV export<\/strong>: download jobs and run history.<\/li>\n<li><strong>Developer tools<\/strong>: WP-CLI commands and a REST API (see Technical details below).<\/li>\n<\/ul>\n\n<h4>Good to know<\/h4>\n\n<ul>\n<li>Alerts are off until you turn them on.<\/li>\n<li>Nothing is sent to anyone but you. No tracking, no ads.<\/li>\n<li>Deleting the plugin removes everything it created.<\/li>\n<\/ul>\n\n<h3>Technical details<\/h3>\n\n<h4>WP-CLI<\/h4>\n\n<ul>\n<li><code>wp cronmon runs<\/code>: recorded runs. Filters: <code>--hook<\/code>, <code>--status<\/code>, <code>--since<\/code>, <code>--limit<\/code>.<\/li>\n<li><code>wp cronmon health<\/code>: the Cron Health findings. Exits non-zero when one is critical.<\/li>\n<li><code>wp cronmon orphans<\/code>: every event with its safe-to-delete verdict.<\/li>\n<\/ul>\n\n<p>All three accept <code>--format=json<\/code>.<\/p>\n\n<h4>REST API<\/h4>\n\n<p>Both routes require <code>manage_options<\/code>. Use Application Passwords over HTTPS.<\/p>\n\n<ul>\n<li><code>GET \/wp-json\/cronmon\/v1\/health<\/code>: the Cron Health findings, a <code>critical<\/code> count and an <code>ok<\/code>\nboolean for Uptime Kuma, Zabbix, Better Stack and similar monitors.<\/li>\n<li><code>GET \/wp-json\/cronmon\/v1\/runs?status=fatal&amp;since=-1%20hour&amp;limit=20<\/code>: recorded runs. Timestamps\nare ISO 8601 UTC.<\/li>\n<\/ul>\n\n<h4>Logging from your own code<\/h4>\n\n<p>Inside a cron callback, attach a note to the current run:<\/p>\n\n<pre><code>do_action( 'cronmon_log', 'reindexed 412 products' );\n<\/code><\/pre>\n\n<p>With Cron Monitor inactive, the call does nothing.<\/p>\n\n<h4>Server cron setup<\/h4>\n\n<p>Add to <code>wp-config.php<\/code>:<\/p>\n\n<pre><code>define( 'DISABLE_WP_CRON', true );\n<\/code><\/pre>\n\n<p>Then add a system cron entry, every five minutes:<\/p>\n\n<pre><code>*\/5 * * * * cd \/path\/to\/wordpress &amp;&amp; wp cron event run --due-now &gt; \/dev\/null 2&gt;&amp;1\n<\/code><\/pre>\n\n<p>Or, without WP-CLI:<\/p>\n\n<pre><code>*\/5 * * * * wget -q -O - https:\/\/example.com\/wp-cron.php?doing_wp_cron &gt; \/dev\/null 2&gt;&amp;1\n<\/code><\/pre>\n\n<p>Cron Monitor never writes to <code>wp-config.php<\/code>.<\/p>\n\n<h4>How it works<\/h4>\n\n<ul>\n<li>A run is recorded when it starts, not when it finishes, so crashes and timeouts still leave a\nrecord.<\/li>\n<li>Cron Monitor attaches itself to every scheduled hook, so a hook with no callbacks now fires and\nis recorded as \"0 callbacks\". Nothing else changes.<\/li>\n<li>Duplicates are matched on hook, arguments and schedule together. One-off events are never\nremoved automatically.<\/li>\n<li>Late-running cron is traced to the job that held the cron lock too long, and a\n  WP_CRON_LOCK_TIMEOUT value is recommended.<\/li>\n<\/ul>\n\n<h4>Performance<\/h4>\n\n<ul>\n<li>Front-end page views load two small PHP files and run no queries. With alerts on, one or two\nqueries are added unless you have a persistent object cache.<\/li>\n<li>Each cron job adds two database writes, one when it starts and one when it finishes.<\/li>\n<li>Measured instrumentation cost is about 0.005 ms and 2.7 KB per cron job (PHP 8.4, one machine).<\/li>\n<\/ul>\n\n<h4>What this plugin stores<\/h4>\n\n<p>Two database tables per site:<\/p>\n\n<ul>\n<li><code>cronmon_runs<\/code>: one row per run. Hook name, an md5 hash of the arguments (never the values),\ntiming, memory, query count, outcome, and the first error message (up to 2,000 characters).<\/li>\n<li><code>cronmon_hooks<\/code>: one row per scheduled event, with the callbacks last seen on it and who owns\nthem.<\/li>\n<\/ul>\n\n<p>Settings live in the <code>cronmon_settings<\/code> option. The stopped-running check uses one transient and\none small option. Successful runs are kept for 7 days and failures for 14, capped at 50,000 rows.\nAll of it is removed on uninstall.<\/p>\n\n<p>No third-party libraries are bundled and nothing is minified.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin contacts no third-party service of its own. No analytics, no update checker. It makes\nat most two kinds of outbound request, both under your control:<\/p>\n\n<ul>\n<li><strong>Alert webhook<\/strong> (optional, off by default): sent to the URL you enter, typically Slack or\nDiscord. The JSON body contains the hook name, event type, start time, duration and, for a\nfailure, the truncated error message.<\/li>\n<li><strong>Loopback check<\/strong>: the Cron Health screen, Site Health test and REST health route can ask your\nown <code>wp-cron.php<\/code> whether it answers. This goes to your own <code>site_url()<\/code>. It never runs during\ncron or WP-CLI, is cached for an hour, and is skipped where something else manages cron\n(Cavalcade, Cron Control, <code>DISABLE_WP_CRON<\/code>, <code>ALTERNATE_WP_CRON<\/code>).<\/li>\n<\/ul>\n\n<p>Alert emails are sent through <code>wp_mail()<\/code> to the address you enter, using your site's own mail\nsetup.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install the plugin from Plugins \u2192 Add New, or upload it to <code>\/wp-content\/plugins\/mcron-monitor\/<\/code>.<\/li>\n<li>Activate it.<\/li>\n<li>Go to <strong>Tools \u2192 Cron Monitor<\/strong>.<\/li>\n<\/ol>\n\n<p>Duplicates and the health check work straight away. Run history fills in as your jobs run.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"where%20do%20i%20see%20my%20cron%20jobs%3F\"><h3>Where do I see my cron jobs?<\/h3><\/dt>\n<dd><p>Go to <strong>Tools \u2192 Cron Monitor<\/strong>. <em>Scheduled Events<\/em> lists every job. <em>Run History<\/em> shows what has\nactually run.<\/p><\/dd>\n<dt id=\"which%20plugin%20created%20this%20cron%20job%3F\"><h3>Which plugin created this cron job?<\/h3><\/dt>\n<dd><p>Check the <strong>Owner<\/strong> column. \"Not observed yet\" means the job has not run since you installed\nCron Monitor. Check back after it runs.<\/p><\/dd>\n<dt id=\"is%20this%20cron%20job%20safe%20to%20delete%3F\"><h3>Is this cron job safe to delete?<\/h3><\/dt>\n<dd><p>Cron Monitor watches each job as it runs and tells you. \"0 callbacks observed across 14 runs\" is\nsafe to delete. \"Callbacks observed during cron\" is not. If it has not seen the job run yet, it\nmakes no recommendation.<\/p><\/dd>\n<dt id=\"why%20is%20my%20cron%20job%20running%20twice%3F\"><h3>Why is my cron job running twice?<\/h3><\/dt>\n<dd><p>It is almost always scheduled twice. The Events screen flags duplicates and shows exactly what it\nwould remove before removing anything.<\/p><\/dd>\n<dt id=\"why%20is%20wp-cron%20not%20running%3F\"><h3>Why is WP-Cron not running?<\/h3><\/dt>\n<dd><p>Open the <strong>Cron Health<\/strong> screen. It checks the usual causes and tells you what to fix. The most\ncommon one is a low-traffic site: WP-Cron only runs when someone visits. The fix is a real server\ncron job, and the Health screen gives you the lines to copy.<\/p><\/dd>\n<dt id=\"will%20it%20tell%20me%20when%20a%20cron%20job%20stops%20running%3F\"><h3>Will it tell me when a cron job stops running?<\/h3><\/dt>\n<dd><p>Yes. It checks from normal page visits and alerts you when a job is overdue twice in a row,\nfifteen minutes apart. A site that gets no visits at all cannot check itself, so use the REST\nhealth route with an external monitor for that.<\/p><\/dd>\n<dt id=\"can%20i%20get%20alerts%20by%20email%3F\"><h3>Can I get alerts by email?<\/h3><\/dt>\n<dd><p>Yes. Go to <strong>Settings \u2192 Alerts<\/strong> and enter an email address, a Slack or Discord webhook, or both.\nThe <em>Send test alert<\/em> button checks that they work.<\/p><\/dd>\n<dt id=\"does%20it%20slow%20my%20site%20down%3F\"><h3>Does it slow my site down?<\/h3><\/dt>\n<dd><p>No. On normal page views it does almost nothing. It only does real work while cron jobs run.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20other%20cron%20plugins%3F\"><h3>Does it work with other cron plugins?<\/h3><\/dt>\n<dd><p>Yes. It only watches and reports. It does not take over WordPress cron.<\/p><\/dd>\n<dt id=\"can%20it%20run%20php%20code%20on%20a%20schedule%3F\"><h3>Can it run PHP code on a schedule?<\/h3><\/dt>\n<dd><p>No, and it never will. Running stored code is a security risk.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20woocommerce%3F\"><h3>Does it work with WooCommerce?<\/h3><\/dt>\n<dd><p>Yes. If Action Scheduler is present, failed tasks are logged. WooCommerce is not required.<\/p><\/dd>\n<dt id=\"does%20it%20support%20multisite%3F\"><h3>Does it support multisite?<\/h3><\/dt>\n<dd><p>Yes. Each site has its own history and settings. There is no network-wide dashboard.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.5.6<\/h4>\n\n<ul>\n<li>Added: screenshots of every screen on the plugin page.<\/li>\n<li>Added: an up-to-date translation template (<code>languages\/mcron-monitor.pot<\/code>).<\/li>\n<\/ul>\n\n<h4>1.5.5<\/h4>\n\n<ul>\n<li>Security: event references from the screens' links and forms are strictly validated the moment\nthey are read, and an edit of an event that no longer exists is refused before anything is\nstored.<\/li>\n<li>Security: event references in forms are printed with WordPress's own escaping functions.<\/li>\n<\/ul>\n\n<h4>1.5.4<\/h4>\n\n<ul>\n<li>Fixed: events whose hook name contains unusual characters (angle brackets, percent-encoding,\ndoubled spaces) can now be run, edited, paused, pinned and deleted from the screens; bulk delete\nreports any it could not match.<\/li>\n<li>Fixed: the event edit form's security token is tied to the specific event being edited.<\/li>\n<li>Fixed: a callback that raises huge numbers of PHP warnings no longer makes the run recorder use\nunbounded memory.<\/li>\n<li>Fixed: Run History and Cron Health links keep hook names containing &amp;, + and # intact.<\/li>\n<li>Fixed: the Run History date filter rejects impossible dates.<\/li>\n<li>Fixed: the hourly pruner is rescheduled if an earlier attempt to schedule it failed.<\/li>\n<li>Fixed: on a new install, two requests recording the schedule change log at once can no longer\noverwrite each other's first snapshot.<\/li>\n<\/ul>\n\n<h4>1.5.3<\/h4>\n\n<ul>\n<li>Changed: every database query is now fully prepared, and form and argument input is sanitized\nfield by field, as required by the wordpress.org review.<\/li>\n<li>Fixed: WordPress 6.5 no longer hits an undefined function when the database upgrade runs.<\/li>\n<li>Fixed: activating or upgrading the plugin no longer rewrites every column of its tables.<\/li>\n<li>Fixed: an alert for a fatal cron run is sent in the same request, not on a later page load.<\/li>\n<li>Fixed: a failed table install is no longer recorded as a finished upgrade; it is retried.<\/li>\n<li>Changed: one-off events are no longer offered for duplicate removal.<\/li>\n<li>Fixed: pinning a duplicate occurrence moves the one selected, not its earliest sibling.<\/li>\n<li>Changed: duplicate removal goes through wp_unschedule_event() in batches of at most 100, so\nunschedule filters and concurrent cron changes are respected.<\/li>\n<li>Fixed: alerts queued by concurrent requests are no longer lost or delivered twice.<\/li>\n<li>Fixed: when a cron callback runs a different cron hook, what happens after it returns is\nrecorded against the right run.<\/li>\n<li>Fixed: {} is refused as event arguments; they must be a JSON list.<\/li>\n<li>Fixed: two requests updating the new and disappeared events list at once no longer erase each\nother's changes.<\/li>\n<li>Fixed: hooks whose names share the first 150 characters are no longer merged into one.<\/li>\n<li>Fixed: turning recording on or off no longer undoes a settings save made at the same moment.<\/li>\n<\/ul>\n\n<h4>1.5.2<\/h4>\n\n<ul>\n<li>Changed: the error recorder no longer reads PHP's error-reporting level, as required by the\nwordpress.org review. Errors silenced with @ inside a cron callback are now counted against the\nrun. Notices and deprecations are recorded only when WP_DEBUG is on, matching what WordPress\nitself reports. PHP's own error handling is unchanged.<\/li>\n<li>Fixed: \"Run now\" and pinning a time no longer lose the event if WordPress refuses the new\nschedule. The original occurrence is put back, with a separate error if even that fails, and a\npin is only saved once the event has actually moved.<\/li>\n<li>Changed: webhook alerts are now sent with wp_safe_remote_post(), so a redirect to a local or\nprivate-network address is refused.<\/li>\n<li>Fixed: the p95 duration now uses the nearest-rank percentile; it could previously show the\nslowest run instead.<\/li>\n<li>Fixed: custom schedule intervals must be whole seconds. Values such as 60.9 or 1e3 are now\nrejected instead of silently truncated.<\/li>\n<li>Fixed: callbacks from other plugins whose names merely start with \"cronmon\" (for example\ncronmonitor_run) are no longer hidden as this plugin's own.<\/li>\n<li>Fixed: the Events and Cron Health screens, and the REST and WP-CLI health checks, no longer\nquery this plugin's tables when they are missing.<\/li>\n<li>Fixed: network-activated sites now schedule the hourly retention pruning on every subsite, not\nonly the main site.<\/li>\n<li>Fixed: searching Run History on very large logs no longer stalls the database.<\/li>\n<li>Changed: with alerts on, one fewer database query per page load.<\/li>\n<\/ul>\n\n<h4>1.5.1<\/h4>\n\n<p>Maintenance release from a pre-submission audit. No feature changes and no database change.<\/p>\n\n<ul>\n<li>Fixed: the Scheduled Events, Run History and Schedules screens raised a fatal error on hosts\nbuilt without the mbstring PHP extension. WordPress does not require that extension, so the\naffected screens were unreachable on those hosts.<\/li>\n<li>Fixed: a failure message is no longer carried in the address bar. It travels in a short-lived,\nper-user record instead, so a crafted link can no longer put a stranger's wording inside a real\nWordPress admin notice.<\/li>\n<li>Fixed: a pinned recurring event whose reschedule was refused stopped recurring altogether.\nWordPress now completes the reschedule at its ordinary interval, so the event keeps running and\nonly loses its pinned time for that one cycle.<\/li>\n<li>Fixed: the duplicate-scheduling guard could trigger a \"translation loading triggered too early\"\nnotice on WordPress 6.7 and later, which on a cron request was then recorded as a warning against\nthe run being watched.<\/li>\n<li>Documentation: the outbound requests this plugin can make are now set out under their own\nExternal services heading, and the webhook entry states exactly what the request body contains.<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<p>The Alerts section of the Settings screen is now three switches, and each one hides and disables\neverything that depends on it. Alerting behaves exactly as it did before for anybody who does not\ntouch them.<\/p>\n\n<ul>\n<li>\"Send alerts\" is a slider rather than a checkbox, and switching it off takes the whole section\nwith it \u2014 both channels, both addresses, the throttle and the test button.<\/li>\n<li>Slack and email are separate switches. Switching one off stops that channel delivering while\nleaving the other one alone, and the address is kept, so switching it back on restores it. Before\nthis, silencing a channel meant clearing the field and typing it back in later.<\/li>\n<li>The throttle and the test button appear only once at least one channel is on: with neither there\nis nothing to throttle and nowhere to send a test.<\/li>\n<li>Both channels are on after upgrading. A site that already had a webhook URL or an alert address\nsaved keeps delivering to it with nothing to change.<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<p>Load-time release. Nothing about what the plugin does has changed \u2014 every screen, every alert, every\nrecorded row and every REST and WP-CLI response is identical. What changed is how much of the plugin\nPHP reads on a request that is not using it.<\/p>\n\n<ul>\n<li>A front-end page view now loads 2 PHP files (35,307 bytes) instead of 15 (357,569 bytes), and\ndeclares 1 PHP class instead of 14. The hooks are still attached at exactly the same moment; the\nclass behind each one is loaded only once its callback has fired and its guard has passed.<\/li>\n<li>Measured on the machine it was built on (Windows, PHP 8.4, no opcode cache), the PHP that a\nfront-end request no longer reads and compiles was taking <strong>5.76 ms and 889 KB of memory per\nrequest<\/strong>. It now takes 0.55 ms and 46 KB. With an opcode cache in front of it the time saving is\nsmaller \u2014 that is the point of an opcode cache \u2014 but the work removed is the same work.<\/li>\n<li>Classes are loaded on first use through an autoloader instead of being required up front.<\/li>\n<li>The schema version check on every page load no longer loads the storage class to compare one\ninteger.<\/li>\n<li>On a site with Action Scheduler (WooCommerce and others), the Action Scheduler recorder is no\nlonger loaded on every request \u2014 only in a request that is actually running the queue.<\/li>\n<li>With run recording switched off, a cron request no longer loads the recorder at all.<\/li>\n<li>tests\/test-loadmap.php asserts the new load map, so a future change cannot quietly put it back.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>Run history can be switched off. The toggle sits above the status filters on the Run History\nscreen: with it off no bracket is attached to any cron callback, so nothing is measured, nothing\nis written, and the plugin costs a cron request nothing at all. Action Scheduler logging stops\nwith it \u2014 it writes into the same history, and on a busy store it is the larger of the two.<\/li>\n<li>Everything that reads run history says so while recording is off, rather than showing a number\nthat can no longer move: the Cron Health screen explains it and skips the \"cron has stopped\"\ncheck entirely (that check reads when anything last ran, which a switched-off recorder freezes \u2014\nit would otherwise report a healthy site as dead), the cron-budget breakdown says it is frozen,\nthe retention projection says there is nothing arriving to project from, and the Settings screen\nnames the alerts that cannot fire. The overdue alert, the Scheduled Events screen, Cron Health's\nconfiguration findings and the Site Health tests all read the schedule instead and are unaffected.<\/li>\n<li>The \"Add a custom schedule\" form moved into a panel beside the schedules table.<\/li>\n<li>Fixed: the \"Add event\" button sat three pixels above the buttons next to it in the table\nnavigation.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Alert when scheduled events have stopped running. Checked from ordinary page requests, because\na dead cron runs nothing else; fires only when the same overdue event is seen across two checks\nfifteen minutes apart, so a quiet site with a working cron is never told otherwise.<\/li>\n<li>Email as an alert destination, beside or instead of the webhook. One address, through\n  wp_mail(). The test button now exercises every saved destination.<\/li>\n<li>REST API: <code>GET \/cronmon\/v1\/health<\/code> and <code>GET \/cronmon\/v1\/runs<\/code>, <code>manage_options<\/code>, the same\nfindings and rows as the screens and the CLI.<\/li>\n<li>Fatal alerts now carry the callback's error text under a new <code>error<\/code> key \u2014 in the webhook payload\nas well as in the alert email. Before this, the summary overwrote it and it was lost. A webhook\nconsumer written against 1.1.0 sees one added key; no existing key changed meaning.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Pause a hook without deactivating its plugin. A hook paused by another cron plugin is recognised and\nreported as paused rather than as mysteriously interrupted here, and a run suppressed by ours is\nrecorded, not silently skipped. One limit worth knowing: a callback that registers itself on the\nsame hook while that hook is already firing can still run that one time, because WordPress has\nalready taken its copy of the list being walked.<\/li>\n<li>An event editor: change a scheduled event's arguments, time and recurrence in place, without losing\nits pin or its duplicate guard.<\/li>\n<li>Custom recurring schedules, with a guard against deleting one an event still uses \u2014 the warning\nnames the event.<\/li>\n<li>Export the scheduled events list to CSV.<\/li>\n<li>A loopback probe on the Cron Health screen and in the Site Health test: asks your own <code>wp-cron.php<\/code>\nwhether it answers. Admin-initiated only, cached for an hour, never runs on a cron request or under\nWP-CLI.<\/li>\n<li>Diagnoses why cron spawning might not be happening at all, including Cavalcade, Cron Control,\n  DISABLE_WP_CRON and <code>ALTERNATE_WP_CRON<\/code>.<\/li>\n<li>A stall check for events that are scheduled but have not actually run.<\/li>\n<li>Aggregate blame: cron time over the last seven days attributed to the plugin or theme that owns the\ncallbacks.<\/li>\n<li>A recommended <code>WP_CRON_LOCK_TIMEOUT<\/code> value based on what this site's events actually take.<\/li>\n<li>WP-CLI commands: <code>wp cronmon runs<\/code>, <code>wp cronmon health<\/code> and <code>wp cronmon orphans<\/code>.<\/li>\n<li>Interface pass across all six screens.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First release.<\/li>\n<li>Per-run history: duration, peak memory, query count, callback count and errors for every cron event.<\/li>\n<li>Rows are written when an event starts, so fatals, out-of-memory kills and timeouts are still recorded.<\/li>\n<li>Callback ownership resolved to the owning plugin, theme, mu-plugin or core file.<\/li>\n<li>Verified orphan verdicts based on observed runs, distinguishing a real orphan from a cron-only registration.<\/li>\n<li>Duplicate detection on hook, arguments and schedule together, with dry-run removal.<\/li>\n<li>Optional per-event guard against duplicate recurring schedules.<\/li>\n<li>Lock-starvation diagnosis, including the handover delay between cron processes.<\/li>\n<li>Cron option health, including the autoload size finding.<\/li>\n<li>Optional webhook alerts for fatals, killed runs, interval drift and starvation. Off by default.<\/li>\n<li>Action Scheduler failure logging when Action Scheduler is present. Failures only by default.<\/li>\n<li>Runs triggered by WP-CLI are detected and recorded as such, so they are not mistaken for a web\nrequest. (The <code>wp cronmon<\/code> commands themselves arrived in 1.1.)<\/li>\n<li>Site Health integration using core's own overdue thresholds.<\/li>\n<li>Recurring events that appeared in the last week are badged as new; recurring events that disappeared are listed on the Health screen.<\/li>\n<li>Pin a daily or weekly event to a time of day in the site timezone, held through daylight-saving changes.<\/li>\n<li><code>do_action( 'cronmon_log', ... )<\/code> lets any cron callback attach notes to its own run.<\/li>\n<\/ul>","raw_excerpt":"See what every WordPress cron job did, get alerted when one fails or stops, and clean up duplicate and leftover events.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/371559","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=371559"}],"author":[{"embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/macvej"}],"wp:attachment":[{"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=371559"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=371559"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=371559"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=371559"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=371559"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ca.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=371559"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}