agentsclimarketplace

Wp action scheduler

Skill Lonsdale201/wp-agent-skills/plugin-scaffold/wp-action-scheduler

A community-maintained collection of agent skills for WordPress plugin and theme development.

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill wp-action-scheduler

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Design and review Action Scheduler jobs in WordPress plugins using Action Scheduler 3.9.x public APIs - async, single, recurring, and cron-expression actions; action_scheduler_init load timing; hook/args/group naming; unique and priority parameters; idempotent callbacks; chunked workloads; activation/deactivation cleanup; WooCommerce-bundled or standalone dependency usage; admin and WP-CLI debugging; queue runner limits; and safe operational troubleshooting. Use when a plugin schedules background jobs with as_enqueue_async_action, as_schedule_single_action, as_schedule_recurring_action, as_schedule_cron_action, as_get_scheduled_actions, or integrates with WooCommerce background queues.

SKILL.md

15.7 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it

WordPress plugin: Action Scheduler

Action Scheduler is a WordPress-native job queue used by WooCommerce and many high-volume plugins. It is not just a nicer wp_schedule_event() wrapper: it stores actions in queue tables, tracks status, claims batches, logs attempts, supports groups, and can run through WP-Cron, async loopback requests, admin tools, and WP-CLI.

Use wp-plugin-cron first when the main question is "native WP-Cron or Action Scheduler?". Use this skill once the answer is Action Scheduler or the plugin already depends on it.

When to load references

  • Need copy-ready scheduling/callback patterns for async, single, recurring, cron-expression, unique, chunked, and activation/deactivation flows: read references/api-patterns.md.
  • Debugging stuck queues, failed actions, duplicate jobs, table/schema problems, or WP-CLI/admin operations: read references/operational-debugging.md.

Misconception this skill corrects

"Action Scheduler means the callback will run once, soon, and in order."

Wrong mental model. Action Scheduler is an at-least-once background queue. Jobs can run late, fail, be retried manually, be re-created by recurring schedules, or be triggered by WP-CLI/admin tools. Callbacks must be idempotent and should advance durable state, not rely on "this hook only fires once".

When to use this skill

Trigger when ANY of the following is true:

  • Scheduling with as_enqueue_async_action(), as_schedule_single_action(), as_schedule_recurring_action(), or as_schedule_cron_action().
  • Replacing many WP-Cron single events with a real queue.
  • Building WooCommerce order/customer/subscription/membership background work.
  • Reviewing duplicate actions, stuck pending actions, failed actions, or oversized queue tables.
  • Adding WP-CLI or admin debugging instructions for queued jobs.
  • Deciding whether to use $unique, as_has_scheduled_action(), or as_next_scheduled_action() guards.

Dependency and load timing

Action Scheduler can be present as:

  • WooCommerce bundled package.
  • Standalone plugin.
  • Composer package bundled by another plugin.

Do not assume your plugin owns the loaded version. Action Scheduler registers available versions and initializes the latest registered version. In local 3.9.3, registration happens on plugins_loaded priority 0, initialization loads the procedural API, and action_scheduler_init fires when the store, logger, runner, admin view, and recurring scheduler are ready.

Rules:

  • Register your job callbacks on every request, early enough for runners: add_action( 'myplugin/process_order', ... ) should not be hidden behind an admin-only screen load.
  • Schedule actions on or after action_scheduler_init when scheduling at runtime.
  • In activation hooks, guard with function_exists( 'as_schedule_single_action' ) before calling the API. If Action Scheduler is an optional dependency, fall back to WP-Cron or show an admin notice.
  • Never schedule at plugin file top-level before WordPress and dependencies load.
add_action( 'action_scheduler_init', static function (): void {
    if ( ! as_has_scheduled_action( 'myplugin/hourly_sync', array(), 'myplugin' ) ) {
        as_schedule_recurring_action(
            time() + HOUR_IN_SECONDS,
            HOUR_IN_SECONDS,
            'myplugin/hourly_sync',
            array(),
            'myplugin',
            true
        );
    }
} );

Public API in Action Scheduler 3.9.3

Scheduling functions return an action ID as int; 0 means scheduling failed.

FunctionUse
as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 )Run once as soon as possible.
as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )Run once at/after a Unix timestamp.
as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )Fixed interval recurrence in seconds.
as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 )Cron-expression recurrence.
as_unschedule_action( $hook, $args = array(), $group = '' )Cancel the next pending matching action.
as_unschedule_all_actions( $hook, $args = array(), $group = '' )Cancel all pending matching actions.
as_next_scheduled_action( $hook, $args = null, $group = '' )Return next timestamp, true for async/running, or false.
as_has_scheduled_action( $hook, $args = null, $group = '' )Efficient boolean check for pending/running actions.
as_get_scheduled_actions( $args = array(), $return_format = OBJECT )Query actions by hook, group, status, date, etc.
as_get_datetime_object( $date_string = null, $timezone = 'UTC' )Build AS DateTime object for queries.
as_supports( $feature )Feature detection. In 3.9.3 it supports ensure_recurring_actions_hook.

The $priority parameter is queue priority, not callback priority. Lower numbers run first; 3.9.3 expects 0-255 and defaults to 10.

Hook, args, and group rules

  • Use namespaced hook names: myplugin/process_order, not process_order.
  • Use one stable group per plugin or feature: myplugin, myplugin-import, myplugin-webhooks.
  • Keep action args small and JSON-serializable. The 3.9.3 DB store can keep larger encoded args in extended_args, but validates against an 8000-character encoded limit and still hashes/indexes args for lookup.
  • Pass identifiers, not large DTOs, WC_Order objects, full API payloads, or secrets. Reload current state inside the callback.
  • Callback args are passed positionally with do_action_ref_array( $hook, array_values( $args ) ). Associative keys are for storage/query readability, not named parameter delivery.
  • Unscheduling matches the hook/args/group combination you pass. For per-entity jobs, pass the exact args. For deactivation, prefer canceling a plugin-owned group with an empty hook if the group is exclusive to your plugin.
as_enqueue_async_action(
    'myplugin/process_order',
    array( 'order_id' => 123 ),
    'myplugin'
);

add_action(
    'myplugin/process_order',
    static function ( int $order_id ): void {
        myplugin_process_order( $order_id );
    },
    10,
    1
);

Unique actions

In 3.9.3, the scheduling functions support $unique. The public docs in the local source say a unique action is not scheduled when another pending or running action has the same hook and group parameters. The 3.9.3 DB store matches pending/running uniqueness by hook and group, not by args.

Use $unique = true only for "only one pending/running copy of this hook+group should exist", such as a global reindex, import coordinator, or recurring sync. Do not use it for per-order or per-user jobs under one hook/group unless only one outstanding job for the whole group is intended.

For per-entity duplicate suppression, check the exact args with as_has_scheduled_action() and still make the callback idempotent. That guard is not a substitute for durable state because it is not a hard business lock.

if ( function_exists( 'as_enqueue_async_action' ) ) {
    $ref = new ReflectionFunction( 'as_enqueue_async_action' );

    if ( $ref->getNumberOfParameters() >= 4 ) {
        as_enqueue_async_action( 'myplugin/reindex', array(), 'myplugin', true );
    } elseif ( ! as_has_scheduled_action( 'myplugin/reindex', array(), 'myplugin' ) ) {
        as_enqueue_async_action( 'myplugin/reindex', array(), 'myplugin' );
    }
}

Do not use uniqueness as the only safety mechanism for payment capture, inventory mutation, email send, or external API side effects. The callback must still check durable state.

$args = array( 'order_id' => $order_id );

if ( ! as_has_scheduled_action( 'myplugin/process_order', $args, 'myplugin' ) ) {
    as_enqueue_async_action( 'myplugin/process_order', $args, 'myplugin' );
}

Idempotent callback pattern

add_action(
    'myplugin/capture_payment',
    static function ( int $order_id ): void {
        $order = wc_get_order( $order_id );
        if ( ! $order ) {
            return;
        }

        if ( 'yes' === $order->get_meta( '_myplugin_payment_captured', true ) ) {
            return;
        }

        myplugin_capture_payment_for_order( $order );

        $order->update_meta_data( '_myplugin_payment_captured', 'yes' );
        $order->save();
    },
    10,
    1
);

For failures that should be retried or surfaced, throw an exception. For permanent no-op cases, return cleanly. Swallowing all exceptions makes failures look complete and hides broken jobs from the admin UI and logs.

Recurring actions

For plugin-owned recurring actions:

  • Register the callback on every request.
  • Schedule idempotently on activation and/or action_scheduler_init.
  • Clear on deactivation with exact hook, args, and group.
  • Use action_scheduler_ensure_recurring_actions in AS 3.9.3+ when you need a repair hook for recurring actions that may have been deleted manually.
add_action( 'action_scheduler_ensure_recurring_actions', static function (): void {
    if ( ! function_exists( 'as_supports' ) || ! as_supports( 'ensure_recurring_actions_hook' ) ) {
        return;
    }

    if ( ! as_has_scheduled_action( 'myplugin/hourly_sync', array(), 'myplugin' ) ) {
        as_schedule_recurring_action(
            time() + HOUR_IN_SECONDS,
            HOUR_IN_SECONDS,
            'myplugin/hourly_sync',
            array(),
            'myplugin',
            true
        );
    }
} );

Statuses, tables, and runner model

Core statuses in 3.9.3:

  • pending
  • in-progress
  • complete
  • failed
  • canceled

The DB store uses prefixed tables based on these base names:

  • actionscheduler_actions
  • actionscheduler_claims
  • actionscheduler_groups
  • actionscheduler_logs

Do not write direct SQL for normal plugin behavior. Use the public API, admin UI, or WP-CLI. Direct SQL is acceptable only for emergency diagnostics with a backup and site-specific approval.

The default queue runner schedules WP-Cron hook action_scheduler_run_queue every minute and can dispatch async admin-context requests on shutdown. Default web runner batch size is filterable through action_scheduler_queue_runner_batch_size and defaults to 25.

WP-CLI and admin debugging

Admin UI: Tools -> Scheduled Actions.

Common WP-CLI commands in 3.9.3:

wp action-scheduler action list --group=myplugin --status=pending
wp action-scheduler action next myplugin/process_order --group=myplugin
wp action-scheduler action get 123 --format=json
wp action-scheduler action logs 123
wp action-scheduler action run 123
wp action-scheduler run --group=myplugin --batch-size=25 --batches=1
wp action-scheduler clean --status=complete,canceled --before='31 days ago'
wp action-scheduler fix-schema

Use WP-CLI for deterministic local/dev runs and for production debugging when the web runner is too slow or loopback requests are blocked.

Critical rules

  • Use action_scheduler_init as the safe runtime scheduling point.
  • Register callbacks on every request, not only inside admin pages or AJAX handlers.
  • Keep args small, scalar/array, JSON-serializable, and non-sensitive.
  • Treat args as positional callback params; array keys are not named params.
  • Prefer plugin-prefixed hook names and stable group names.
  • Use $unique = true only for hook+group singleton suppression; still make callbacks idempotent.
  • Use as_has_scheduled_action() for a boolean guard; use as_next_scheduled_action() only when you need the timestamp.
  • Throw for real job failures; return for permanent no-op cases.
  • Do not mutate AS tables directly in normal plugin code.
  • For large workloads, enqueue chunks/cursors instead of one massive action.

Common mistakes

// WRONG - callback hidden in an admin screen, runner cannot find it from WP-Cron.
if ( is_admin() && isset( $_GET['page'] ) && 'myplugin' === $_GET['page'] ) {
    add_action( 'myplugin/process_order', 'myplugin_process_order' );
}

// WRONG - passes a large payload and secrets through queued args.
as_enqueue_async_action( 'myplugin/send_payload', $full_api_payload, 'myplugin' );

// WRONG - associative args treated as named callback parameters.
add_action( 'myplugin/process_order', static function ( array $args ): void {
    myplugin_process_order( $args['order_id'] );
}, 10, 1 );

// RIGHT - pass ID, reload state, receive positional callback arg.
as_enqueue_async_action(
    'myplugin/process_order',
    array( 'order_id' => $order_id ),
    'myplugin'
);

add_action( 'myplugin/process_order', 'myplugin_process_order', 10, 1 );

// WRONG - this only matches empty-arg actions for this hook+group. It will not
// clear per-order jobs scheduled with array( 'order_id' => $order_id ).
as_unschedule_all_actions( 'myplugin/process_order', array(), 'myplugin' );

// RIGHT - exact hook + args + group for one per-entity job.
as_unschedule_all_actions(
    'myplugin/process_order',
    array( 'order_id' => $order_id ),
    'myplugin'
);

// RIGHT - on deactivation, clear all pending actions in an exclusive
// plugin-owned group.
as_unschedule_all_actions( '', array(), 'myplugin' );

Cross-references

  • Run wp-plugin-cron before this when choosing between WP-Cron and Action Scheduler.
  • Run wp-plugin-lifecycle for activation/deactivation structure and multisite activation behavior.
  • Run wp-plugin-dto when queued args should hydrate a stable input object inside the callback.
  • Run wp-plugin-presenter when an action produces admin/REST/email output.
  • Run wp-security-audit for callbacks processing persisted IDs, external API payloads, or user-supplied data.

What this skill does NOT cover

  • Building an external queue on Redis, SQS, RabbitMQ, or Beanstalkd.
  • Forking or replacing Action Scheduler internals.
  • Native WP-Cron basics; use wp-plugin-cron.
  • WooCommerce-specific business rules for orders/subscriptions/memberships; combine this with the relevant WooCommerce skill.

Source notes

Validated against the local Action Scheduler 3.9.3 plugin installed at wp-content/plugins/action-scheduler on 2026-04-29:

  • functions.php public API signatures.
  • ActionScheduler::init() and action_scheduler_init timing.
  • ActionScheduler_Action::execute() positional arg delivery.
  • ActionScheduler_Store statuses and DB store args length behavior.
  • ActionScheduler_QueueRunner runner hook and batch size.
  • WP-CLI command classes under classes/WP_CLI.

References

Gives 0 of the 12 instructions most debug triage skills give in ~3.6k tokens

Counted across 839 of the 1,149 authors here whose files we hold, read 2026-08-06

  • investigate root cause before proposing any fixin 102 of 839, across 65 files
  • read error messages completelyin 90 of 839, across 48 files
  • create a failing test case before fixingin 84 of 839, across 44 files
  • reproduce the issue consistentlyin 82 of 839, across 40 files
  • change one variable at a timein 82 of 839, across 42 files
  • check recent changesin 74 of 839, across 35 files
  • write the regression test before fixingin 74 of 839, across 36 files
  • fix the root cause not the symptomin 60 of 839, across 43 files
  • implement a single fix at a timein 59 of 839, across 20 files
  • trace data flow backward to the sourcein 50 of 839, across 20 files
  • remove all debug instrumentationin 49 of 839, across 13 files
  • form a single hypothesisin 48 of 839, across 18 files

Said here and by no other author read

  • schedule runtime actions after action scheduler initializes
  • register job callbacks on every request
  • guard function calls during plugin activation
  • use namespaced hook and stable group names
  • keep action arguments small and json serializable
  • pass identifiers instead of full objects in arguments

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.