From 1.17.0 (#147545), any ability that changes something can say which task it is working on. LH_Tasks_ability_task_context_class (includes/lh-tasks-ability-task-context-class.php) adds two optional parameters, task_id and claim_token, to the input schema of every ability annotated readonly: false, on every site. Because they are declared in the schema, they show up in each ability’s documentation, are type-checked, and work with closed (additionalProperties: false) inputs.
wp_ability_normalize_input, so the ability’s own callbacks never see them. Giving only one of the two fails with lh_tasks_task_context_incomplete.wp_ability_validate_input, before permissions and before anything runs. If the caller does not hold the claim with that token, the call fails with lh_tasks_not_your_claim (or lh_tasks_not_found). Validation is used rather than the permission filter because core replaces any permission error with a generic message, which would hide the reason from the agent.wp_ability_execute_result, through renew_task() with no note, so no heartbeat entry is logged. A result is a failure if it is a WP_Error or an array with success false; failures never renew. Staged proposals count as progress, so a successful propose-* call renews too._lh_tasks-ability_calls is a running count and _lh_tasks-last_ability holds the last ability’s name, GMT time, success and whether it renewed. Refused calls are not counted. This replaces a progress-log entry per call, which would bloat the log.Automatic renewal stops once the claim is older than the maximum lease, 4 hours by default (filter lh_tasks_return_max_claim_lease_seconds, at least a minute). The lease runs from _lh_tasks-claim_started, set on lh_tasks_task_claimed; a claim made before 1.17.0 gets its start time on its first automatic renewal. Past the lease, writes still run and are still counted, but the stale-claim sweep releases the claim as normal and the task has to be claimed again, which starts a new lease. An explicit renew-claim is not capped.
An ability takes part when it is annotated readonly: false, its input schema is an object, and it does not already define task_id or claim_token. Unannotated abilities, mcp-adapter/* (the execute-ability proxy passes its parameters to the target ability, where the hooks fire under the target’s own name) and lh-tasks’ own abilities are left alone. The filter lh_tasks_ability_takes_task_context ($takes, $name, $args) can change the decision, which is made once per request at registration.
Tasks normally live on the main site, while abilities run on any site. If task_id is not a task on the current site, it is looked up on the main site, and the claim check, renewal and counting all run there (switch_to_blog()). Post IDs collide between sites, so the current site is checked first by post type.
The input, validation and result filters were added to the Abilities API in WordPress 7.1, so lh-tasks requires 7.1 from this release.