Skills / Data & Science

diagnostic-cron-analysis

Analyzes WordPress wp-cron scheduled events for health issues including overdue events (>1 hour), duplicate hook registrations, and excessively frequent intervals (<5 minutes). Requires WP-CLI. Self-gates when WP-CLI is unavailable.

Source Shaisolaris/solaris-dev-shopLicense MITSafety scan Needs review
1 finding in 1 files. Read them before installing.
  • Installs something that runs on its own later · employees/specialized/wordpress-master/diagnostics/diagnostic-cron-analysis/SKILL.md:124FINDINGS_OVERDUE+=("{\"id\":\"PERF-CRON-OVRD\",\"severity\":\"${SEVERITY}\",\"category\":\"Performance\",\"title\":\"Overdue wp-cron event: ${HOOK} (${OVERDUE_D

This is a pattern scan of the skill folder, not a judgment of intent. Some matches are legitimate (an installer, a quoted warning). Checked on Oct 20, 2026. How the scan works.

npx degit Shaisolaris/solaris-dev-shop/employees/specialized/wordpress-master/diagnostics/diagnostic-cron-analysis .claude/skills/diagnostic-cron-analysis

12 alternatives · Best data & science skills · Author? Prove this skill

SKILL.md
# Diagnostic Skill: wp-cron Event Analysis

You analyze WordPress scheduled events (wp-cron) for three categories of health issues: overdue events, duplicate hook registrations, and excessively frequent intervals. WordPress cron is pseudo-cron - events only fire when a visitor loads the site, so problems can accumulate silently.

## Overview

WordPress wp-cron is triggered by page loads, not a real system cron daemon. This means:

- **Overdue events** - If traffic is low (overnight, weekends), scheduled events may not run on time. Events overdue by more than an hour indicate a cron health problem that could affect backups, email sending, plugin updates, and other time-sensitive operations.
- **Duplicate hooks** - Plugins sometimes register the same cron hook multiple times, either due to bugs or missed deregistration during deactivation. This causes redundant execution and unnecessary server load.
- **Excessive frequency** - Some plugins register cron events with intervals under 5 minutes (300 seconds). Events running every 30-60 seconds add persistent background load, even for low-traffic sites.

**Source type note:** This skill requires a live WordPress database. It must NOT run on `git` source types (no live DB). It self-gates when `WP_CLI_AVAILABLE` is false.

## Prerequisites

Before running checks, you need:
- Site connection profile from `sites.json` (loaded by CoWork or /diagnose)
- `WP_CLI_PREFIX` set by the /diagnose command's Source-Type Routing section
- `WP_CLI_AVAILABLE=true` (this skill requires WP-CLI for all checks)
- Source type must NOT be `git`

## Self-Gate: WP-CLI Availability Check

**This is the first thing to run.** If `WP_CLI_AVAILABLE` is false, emit the skip finding and stop immediately:

```bash
if [ "$WP_CLI_AVAILABLE" != "true" ]; then
  echo '[{"id":"PERF-CRON-SKIP","severity":"Info","category":"Performance","title":"Cron analysis skipped - WP-CLI not available","summary":"Cron event analysis requires WP-CLI to query the database.","detail":"WP_CLI_AVAILABLE is false. This check requires running wp cron event list which needs WP-CLI and a live WordPress database. Git sources and sources without WP-CLI configured cannot query cron event data.","location":"wp-cron","fix":"Install WP-CLI on the server or connect via SSH or Docker source type with WP-CLI available."}]'
  exit 0
fi
```

## Data Collection

Retrieve all scheduled cron events as JSON:

```bash
CRON_EVENTS=$($WP_CLI_PREFIX cron event list \
  --fields=hook,next_run_gmt,recurrence,interval,schedule \
  --format=json 2>/dev/null)

# Validate JSON response
if [ -z "$CRON_EVENTS" ] || ! echo "$CRON_EVENTS" | jq empty 2>/dev/null; then
  echo '[{"id":"PERF-CRON-ERR","severity":"Warning","category":"Performance","title":"Cron event list failed","summary":"Could not retrieve wp-cron events from the database.","detail":"wp cron event list returned empty or invalid JSON output. This may indicate a database connectivity issue, a corrupted cron option, or WP-CLI version incompatibility.","location":"wp-cron","fix":"Verify WP-CLI database access with: wp db check. Ensure the cron option exists with: wp option get cron. If the cron option is corrupted, consider: wp cron event run --due-now to attempt recovery."}]'
  exit 0
fi

# Get current UTC epoch for overdue comparison
CURRENT_EPOCH=$(date +%s)

# Count total events for reporting
TOTAL_EVENTS=$(echo "$CRON_EVENTS" | jq 'length')
```

## Cross-Platform Date Conversion

`next_run_gmt` is returned as a UTC string (e.g. `"2024-01-15 03:00:00"`). Convert to Unix epoch for comparison. Handle the difference between Linux `date -d` and macOS `date -j -f`:

```bash
# Cross-platform epoch conversion function
gmt_to_epoch() {
  local gmt_str="$1"
  # Try Linux date -d first
  local epoch
  epoch=$(date -d "$gmt_str" +%s 2>/dev/null)
  if [ -n "$epoch" ] && [ "$epoch" != "0" ]; then
    echo "$epoch"
    return
  fi
  # Fall back to macOS date -j -f
  epoch=$(date -j -f "%Y-%m-%d %H:%M:%S" "$gmt_str" +%s 2>/dev/null)
  if [ -n "$epoch" ]; then
    echo "$epoch"
    return
  fi
  # Both failed - return 0 (will not trigger overdue threshold)
  echo "0"
}
```

---

## Check 1: Overdue Events (>1 hour = 3600 seconds past next_run_gmt)

An event is overdue when its `next_run_gmt` is in the past by more than 3600 seconds (1 hour). Severity escalates to Critical if overdue by more than 24 hours (86400 seconds).

```bash
FINDINGS_OVERDUE=()

# Get events where next_run_gmt appears to be past events (use jq to extract candidates)
# Process each event individually for cross-platform date conversion
while IFS= read -r event_json; do
  HOOK=$(echo "$event_json" | jq -r '.hook')
  NEXT_RUN=$(echo "$event_json" | jq -r '.next_run_gmt')
  SCHEDULE=$(echo "$event_json" | jq -r '.schedule // "Non-repeating"')

  EVENT_EPOCH=$(gmt_to_epoch "$NEXT_RUN")

  # Skip if epoch conversion failed (returns 0)
  if [ "$EVENT_EPOCH" -eq 0 ]; then
    continue
  fi

  OVERDUE_SECONDS=$((CURRENT_EPOCH - EVENT_EPOCH))

  if [ "$OVERDUE_SECONDS" -gt 3600 ]; then
    OVERDUE_HOURS=$(awk "BEGIN {printf \"%.1f\", $OVERDUE_SECONDS / 3600}")

    if [ "$OVERDUE_SECONDS" -gt 86400 ]; then
      SEVERITY="Critical"
      OVERDUE_DAYS=$(awk "BEGIN {printf \"%.1f\", $OVERDUE_SECONDS / 86400}")
      OVERDUE_DESC="${OVERDUE_DAYS} days"
    else
      SEVERITY="Warning"
      OVERDUE_DESC="${OVERDUE_HOURS} hours"
    fi

    FINDINGS_OVERDUE+=("{\"id\":\"PERF-CRON-OVRD\",\"severity\":\"${SEVERITY}\",\"category\":\"Performance\",\"title\":\"Overdue wp-cron event: ${HOOK} (${OVERDUE_DESC} overdue)\",\"summary\":\"A scheduled WordPress cron event has not run and is significantly overdue, which may indicate a broken cron system.\",\"detail\":\"Hook '${HOOK}' (schedule: ${SCHEDULE}) was due at ${NEXT_RUN} UTC and is ${OVERDUE_DESC} overdue. wp-cron events only run when the site receives visitor traffic - on low-traffic sites, events can fall significantly behind. If multiple events are overdue, consider setting up a real system cron: \\\"wp cron event run\\\" triggered every 5 minutes via crontab.\",\"location\":\"wp-cron\",\"fix\":\"For immediate catch-up: wp cron event run ${HOOK}. To prevent future overdue events on low-traffic sites, add a real system cron via crontab: \\\"*/5 * * * * curl -s https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null\\\" or use WP-CLI: \\\"*/5 * * * * wp cron event run --due-now\\\"\"}")
  fi
done < <(echo "$CRON_EVENTS" | jq -c '.[]')
```

**Finding ID:** `PERF-CRON-OVRD` - one finding per overdue event, hook name in title and detail.

**Severity thresholds:**
- `Warning`: overdue by 1-24 hours
- `Critical`: overdue by more than 24 hours

---

## Check 2: Duplicate Hooks (same hook name registered more than once)

Find cron hooks appearing more than once in the event list. This indicates a plugin registered itself multiple times without deregistering first, or failed to clean up on deactivation.

```bash
DUPLICATES=$(echo "$CRON_EVENTS" | jq \
  '[group_by(.hook)[] | select(length > 1) | {hook: .[0].hook, count: length, events: .}]')

FINDINGS_DUPLICATES=()

# Process each duplicate group
while IFS= read -r dup_json; do
  HOOK=$(echo "$dup_json" | jq -r '.hook')
  COUNT=$(echo "$dup_json" | jq -r '.count')
  SCHEDULES=$(echo "$dup_json" | jq -r '.events[].schedule // "Non-repeating"' | sort | uniq | tr '\n' ', ' | sed 's/,$//')

  FINDINGS_DUPLICATES+=("{\"id\":\"PERF-CRON-DUP\",\"severity\":\"Warning\",\"category\":\"Performance\",\"title\":\"Duplicate wp-cron hook: ${HOOK} (${COUNT} instances)\",\"summary\":\"The same cron hook is registered multiple times, causing redundant executions and unnecessary server load.\",\"detail\":\"Hook '${HOOK}' is registered ${COUNT} times in the cron table (schedules: ${SCHEDULES}). This typically indicates a plugin that adds itself multiple times without deregistering first, or missed deregistration during plugin deactivation. Each registered instance will fire independently, multiplying the work done by that cron handler.\",\"location\":\"wp-cron\",\"fix\":\"Identify which plugin registers this hook (search codebase for wp_schedule_event with hook name '${HOOK}'). Ensure the plugin calls wp_clear_scheduled_hook before registering a new event, and removes events on deactivation. To manually clear all instances: wp cron event delete ${HOOK}\"}")
done < <(echo "$DUPLICATES" | jq -c '.[]' 2>/dev/null)
```

**Finding ID:** `PERF-CRON-DUP` - one finding per duplicate hook name, not per duplicate instance.

**Example finding:**
```json
{
  "id": "PERF-CRON-DUP",
  "severity": "Warning",
  "category": "Performance",
  "title": "Duplicate wp-cron hook: publish_future_post (3 instances)",
  "summary": "The same cron hook is registered multiple times, causing redundant executions and unnecessary server load.",
  "detail": "Hook 'publish_future_post' is registered 3 times in the cron table (schedules: hourly). This typically indicates a plugin that adds itself multiple times without deregistering first, or missed deregistration during plugin deactivation. Each registered instance will fire independently, multiplying the work done by that cron handler.",
  "location": "wp-cron",
  "fix": "Identify which plugin registers this hook (search codebase for wp_schedule_event with hook name 'publish_future_post'). Ensure the plugin calls wp_clear_scheduled_hook before registering a new event, and removes events on deactivation. To manually clear all instances: wp cron event delete publish_future_post"
}
```

---

## Check 3: Excessive Frequency (interval < 300 seconds = 5 minutes)

Find recurring events with intervals under 5 minutes. **Critical:** Only check recurring events - events with `interval: 0` or `recurrence: "Non-repeating"` run once and must be excluded from this check.

The `interval > 0` condition in the jq filter enforces this:

```bash
FREQUENT=$(echo "$CRON_EVENTS" | jq \
  '[.[] | select(.interval > 0 and .interval < 300)]')

FINDINGS_FREQUENT=()

while IFS= read -r freq_json; do
  HOOK=$(echo "$freq_json" | jq -r '.hook')
  INTERVAL=$(echo "$freq_json" | jq -r '.interval')
  SCHEDULE=$(echo "$freq_json" | jq -r '.schedule // "custom"')
  INTERVAL_DISPLAY="${INTERVAL}s"

  # Format interval for human readability
  if [ "$INTERVAL" -ge 60 ]; then
    INTERVAL_MIN=$(awk "BEGIN {printf \"%.0f\", $INTERVAL / 60}")
    INTERVAL_DISPLAY="${INTERVAL_MIN} minutes (${INTERVAL}s)"
  fi

  FINDINGS_FREQUENT+=("{\"id\":\"PERF-CRON-FREQ\",\"severity\":\"Warning\",\"category\":\"Performance\",\"title\":\"Excessive cron frequency: ${HOOK} (every ${INTERVAL_DISPLAY})\",\"summary\":\"A cron event is scheduled to run more frequently than every 5 minutes, adding persistent background load to the server.\",\"detail\":\"Hook '${HOOK}' (schedule: ${SCHEDULE}) fires every ${INTERVAL_DISPLAY}. Events running more frequently than every 5 minutes (300 seconds) create persistent background load - on shared hosting, this can trigger rate limiting or host complaints. On any server, sub-5-minute intervals are rarely justified by actual business requirements.\",\"location\":\"wp-cron\",\"fix\":\"Review the plugin that registers '${HOOK}' and determine if this frequency is genuinely required. Most tasks (cache refreshes, feed updates, health checks) can safely run every 15-60 minutes. To change the interval, the plugin's source code or its settings must be updated - intervals are not configurable via wp-cron directly. If the plugin is abandoned or misconfigured, consider: wp cron event delete ${HOOK}\"}")
done < <(echo "$FREQUENT" | jq -c '.[]' 2>/dev/null)
```

**Finding ID:** `PERF-CRON-FREQ` - one finding per excessively frequent event.

**Threshold:** `interval > 0 AND interval < 300` - the `interval > 0` guard excludes non-repeating events.

---

## Clean Finding (no issues detected)

When no overdue events, no duplicates, and no excessive frequency events are found, emit a single Info finding:

```json
{
  "id": "PERF-CRON-OK",
  "severity": "Info",
  "category": "Performance",
  "title": "wp-cron schedule is healthy",
  "summary": "No overdue, duplicate, or excessively frequent cron events detected.",
  "detail": "Analyzed {COUNT} scheduled cron events. All events are on-schedule, unique, and use reasonable intervals (5 minutes or longer). No action required.",
  "location": "wp-cron",
  "fix": "No action required. Monitor cron health if traffic drops significantly (overdue events may accumulate on low-traffic sites)."
}
```

---

## Output Assembly

Collect all findings and emit as a JSON array:

```bash
ALL_FINDINGS=()

# Add overdue findings
ALL_FINDINGS+=("${FINDINGS_OVERDUE[@]}")

# Add duplicate findings
ALL_FINDINGS+=("${FINDINGS_DUPLICATES[@]}")

# Add excessive frequency findings
ALL_FINDINGS+=("${FINDINGS_FREQUENT[@]}")

# If no issues found, emit clean finding
if [ ${#ALL_FINDINGS[@]} -eq 0 ]; then
  CLEAN="{\"id\":\"PERF-CRON-OK\",\"severity\":\"Info\",\"category\":\"Performance\",\"title\":\"wp-cron schedule is healthy\",\"summary\":\"No overdue, duplicate, or excessively frequent cron events detected.\",\"detail\":\"Analyzed ${TOTAL_EVENTS} scheduled cron events. All events are on-schedule, unique, and use reasonable intervals (5 minutes or longer).\",\"location\":\"wp-cron\",\"fix\":\"No action required.\"}"
  ALL_FINDINGS+=("$CLEAN")
fi

# Build JSON array
printf '['
for i in "${!ALL_FINDINGS[@]}"; do
  printf '%s' "${ALL_FINDINGS[$i]}"
  if [ $i -lt $((${#ALL_FINDINGS[@]} - 1)) ]; then
    printf ','
  fi
done
printf ']'
```

---

## Output Format

Return findings as a JSON array. Each triggered check generates one finding per affected event (overdue/duplicate/frequent). All findings must include these 8 fields:

- `id` (string) - `PERF-CRON-OVRD`, `PERF-CRON-DUP`, `PERF-CRON-FREQ`, `PERF-CRON-OK`, `PERF-CRON-SKIP`, or `PERF-CRON-ERR`
- `severity` (string) - `"Critical"`, `"Warning"`, or `"Info"`
- `category` (string) - `"Performance"`
- `title` (string) - Short descriptive title including hook name
- `summary` (string) - One non-technical sentence explaining the finding
- `detail` (string) - Technical detail with exact values, intervals, and context
- `location` (string) - `"wp-cron"`
- `fix` (string) - Specific remediation steps with WP-CLI commands

**Example complete output (mixed issues):**
```json
[
  {
    "id": "PERF-CRON-OVRD",
    "severity": "Critical",
    "category": "Performance",
    "title": "Overdue wp-cron event: wp_scheduled_delete (26.3 days overdue)",
    "summary": "A scheduled WordPress cron event has not run and is significantly overdue, which may indicate a broken cron system.",
    "detail": "Hook 'wp_scheduled_delete' (schedule: daily) was due at 2024-01-15 03:00:00 UTC and is 26.3 days overdue.",
    "location": "wp-cron",
    "fix": "For immediate catch-up: wp cron event run wp_scheduled_delete. To prevent future overdue events, add a real system cron."
  },
  {
    "id": "PERF-CRON-DUP",
    "severity": "Warning",
    "category": "Performance",
    "title": "Duplicate wp-cron hook: my_plugin_cleanup (3 instances)",
    "summary": "The same cron hook is registered multiple times, causing redundant executions and unnecessary server load.",
    "detail": "Hook 'my_plugin_cleanup' is registered 3 times in the cron table (schedules: hourly).",
    "location": "wp-cron",
    "fix": "Identify which plugin registers this hook and ensure it calls wp_clear_scheduled_hook before registering."
  },
  {
    "id": "PERF-CRON-FREQ",
    "severity": "Warning",
    "category": "Performance",
    "title": "Excessive cron frequency: woocommerce_cleanup_sessions (every 2 minutes (120s))",
    "summary": "A cron event is scheduled to run more frequently than every 5 minutes, adding persistent background load to the server.",
    "detail": "Hook 'woocommerce_cleanup_sessions' (schedule: custom) fires every 2 minutes (120s).",
    "location": "wp-cron",
    "fix": "Review the plugin that registers this hook and determine if this frequency is genuinely required."
  }
]
```

## Finding IDs Reference

| ID | Severity | Trigger |
|----|----------|---------|
| PERF-CRON-OVRD | Warning/Critical | Event overdue by >1 hour (Critical if >24 hours) |
| PERF-CRON-DUP | Warning | Same hook name registered more than once |
| PERF-CRON-FREQ | Warning | Recurring event with interval < 300 seconds |
| PERF-CRON-OK | Info | All checks pass - cron schedule is healthy |
| PERF-CRON-SKIP | Info | WP-CLI not available - skill self-gated |
| PERF-CRON-ERR | Warning | wp cron event list returned invalid output |

## Error Handling

Each failed check produces an error finding rather than aborting the entire skill:

- **WP-CLI unavailable:** `PERF-CRON-SKIP` - Info, self-gate, return immediately
- **Event list failure:** `PERF-CRON-ERR` - Warning, explains likely cause, suggests `wp db check`
- **Date conversion failure:** Skip that individual event (epoch returns 0), do not abort

## Notes

- Use `$WP_CLI_PREFIX` for all WP-CLI invocations - never call `wp` directly
- Events with `interval: 0` or `recurrence: "Non-repeating"` are single-shot events; they must NOT be checked for excessive frequency
- The `gmt_to_epoch()` function handles both Linux (`date -d`) and macOS (`date -j -f`) - the fallback matters for local development environments
- One finding per affected event (not one aggregate finding) - the report consumer needs individual hook names to take action
- All `next_run_gmt` values from WP-CLI are in UTC; `date +%s` on the host must also produce UTC epoch (standard behavior on all platforms)

Shown under the source repo’s MIT license. Read the full folder on GitHub.