Skip to content

Health Checks

The health check runner lives under Subscribed\IncidentMonitor\HealthCheck\....

Bootstrap

<?php

use Subscribed\IncidentMonitor\HealthCheck\Bootstrap;

// Boot the health check runtime and register scheduler hooks.
// Migrations are registered through the base-plugin migration hook.
$app = Bootstrap::init();

Or include the package bootstrap.php file and call:

<?php

// Helper function defined by the package bootstrap file.
subscribed_incident_monitor_health_checks();

Registering checks

Register checks through the registry or the package_incident_monitor/checks filter.

<?php

use Subscribed\IncidentMonitor\HealthCheck\Definition\CheckDefinition;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckContext;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckResult;

// Register checks dynamically through a filter.
add_filter('package_incident_monitor/checks', function (array $checks): array {
    $checks[] = new CheckDefinition(
        'orders/stuck-capture',
        'Orders missing capture',
        function (CheckContext $context): CheckResult {
            // Restore the last persisted cursor for this run.
            $offset = (int) $context->state('offset', 0);
            // Batch size comes from the check definition options.
            $limit = $context->batchSize();

            // Load the next slice of work.
            $orderIds = my_expensive_lookup($offset, $limit);
            if (empty($orderIds)) {
                // No issues found, mark the run successful.
                return CheckResult::ok('No uncaptured orders found.', ['processed' => $offset]);
            }

            // Process the current slice.
            foreach ($orderIds as $orderId) {
                inspect_order($orderId);
            }

            if (count($orderIds) === $limit) {
                // Persist the next cursor so Action Scheduler can continue later.
                return CheckResult::continue(
                    'Continuing order scan.',
                    ['offset' => $offset + $limit],
                    ['processed' => $offset + $limit]
                );
            }

            return CheckResult::failed(
                // The third argument becomes the stable incident key.
                'Found uncaptured orders.',
                ['order_ids' => $orderIds],
                'orders/stuck-capture',
                // Optional: a result-level suggested solution, shown in the ticket.
                // Takes priority over the ticket-level suggested_solution fallback below.
                "Re-capture the affected orders manually:

```bash
wp order capture {$orderIds[0]}
```"
            );
        },
        [
            // How often the check becomes due again after a completed run.
            'interval' => '5 minutes',
            // Number of items to inspect per slice.
            'batch_size' => 50,
            // Intended wall time budget for each slice.
            'time_budget' => 10,
            // Retry count before the run is marked as failed.
            'max_attempts' => 3,
            // Finished run retention in days.
            'retention_days' => 14,
            'ticket' => [
                // When enabled, failure incidents are forwarded to IncidentMonitorFacade.
                'enabled' => true,
                // Connector name resolved by the incident tracker bootstrap.
                'connector' => 'asana',
                // Logical project key resolved by the incident tracker bootstrap.
                'project_key' => 'development',
                // Only create/update a ticket after this many consecutive failures.
                'failure_threshold' => 2,
                // Tags passed through to the external ticket.
                'tags' => ['health-check', 'orders'],
                // Optional fallback: shown when CheckResult::failed() does not carry its own
                // suggested_solution. Supports triple-backtick code blocks.
                'suggested_solution' => "Check the capture job logs and re-trigger manually:

```bash
wp order capture <id>
```",
            ],
        ]
    );

    return $checks;
});

Issue tracking setup with health checks

If you want health checks to create tickets automatically, bootstrap IncidentMonitorFacade first and then register checks with ticket.enabled = true.

<?php

use League\Container\Container;
use Subscribed\IncidentMonitor\Container\IncidentMonitorBootstrap;
use Subscribed\IncidentMonitor\Facade\IncidentMonitorFacade;
use Subscribed\IncidentMonitor\HealthCheck\Bootstrap as HealthCheckBootstrap;
use Subscribed\IncidentMonitor\HealthCheck\Definition\CheckDefinition;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckContext;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckResult;

$container = new Container();

// Bootstrap the ticketing layer first so health check incidents have somewhere to go.
IncidentMonitorBootstrap::register($container, [
    'asana_token' => getenv('ASANA_TOKEN'),
    'project_map' => [
        'development' => 'asana-project-id-dev',
    ],
    'default_project_key' => 'development',
]);

IncidentMonitorFacade::setContainer($container);

// Register a health check that opens or updates an external incident ticket.
add_filter('package_incident_monitor/checks', function (array $checks): array {
    $checks[] = new CheckDefinition(
        'billing/missing-capture',
        'Missing captures',
        function (CheckContext $context): CheckResult {
            $missing = find_missing_captures($context->batchSize());

            if (empty($missing)) {
                return CheckResult::ok('No missing captures.');
            }

            return CheckResult::failed(
                'Found payments without capture.',
                ['payment_ids' => $missing],
                'billing/missing-capture'
            );
        },
        [
            'interval' => '10 minutes',
            'ticket' => [
                // Forward incidents into one or more connectors.
                'enabled' => true,
                'connectors' => [
                    [
                        'connector' => 'asana',
                        'project_key' => 'development',
                        'tags' => ['health-check', 'billing'],
                    ],
                    [
                        'connector' => 'sms',
                        'project_key' => 'on-call-primary',
                        'tags' => ['health-check', 'billing', 'urgent'],
                    ],
                ],
                'failure_threshold' => 1,
            ],
        ]
    );

    return $checks;
});

// Start the scheduler hooks and register migrations.
HealthCheckBootstrap::init();

Runner behavior

The runner:

  • schedules due checks through Action Scheduler
  • slices long-running work into resumable batches
  • persists run state between slices
  • retries failed slices
  • emits incidents when failure thresholds are reached

Heavy checks should:

  • keep each slice short
  • return CheckResult::continue() with the next cursor
  • keep per-item work idempotent where possible

WP-CLI

The package exposes health check commands through WP-CLI when it is loaded inside WordPress:

wp subscribed health-checks list
wp subscribed health-checks run billing/missing-capture
wp subscribed health-checks run --all
wp subscribed health-checks run --all --async
wp subscribed health-checks scan

Notes:

  • list shows registered checks and persisted status rows
  • run <check-id> executes the check immediately in the current CLI process
  • run --all --async enqueues all registered checks into Action Scheduler
  • scan performs the normal due-check scan and enqueues only checks that are due

Storage

Status and run history are stored in:

  • {$wpdb->prefix}incident_monitor_health_check_statuses
  • {$wpdb->prefix}incident_monitor_health_check_runs

Hooks

  • package_incident_monitor/checks
  • package_incident_monitor/incident_created
  • package_incident_monitor/incident_updated
  • package_incident_monitor/incident_recovered
  • package_incident_monitor/log
  • package_incident_monitor/max_parallel_checks
  • package_incident_monitor/scan_interval
  • package_incident_monitor/run_retention_days
  • package_incident_monitor/cleanup_statuses
  • package_incident_monitor/default_ticket_project_key
  • package_incident_monitor/default_connector_name