fix(webhooks): keep retries scheduled past the publish boundary#285
Conversation
WordPress publishes a 'future' post immediately when its date is less than MINUTE_IN_SECONDS away, and the shortest webhook retry backoff was scheduled at exactly +60s. A wall-clock second elapsing before WordPress' own check could shave the gap to 59s, publishing the retry early, firing an extra processing pass, and burning one of the 15 attempts. This made Newspack_Test_Webhooks::test_request_max_retries flaky (trashing the request one iteration early). Clamp the delay to a 1-minute minimum and add a few seconds of headroom so a scheduled retry reliably clears the boundary, and add a deterministic regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a timing edge case in the webhook retry scheduler where a retry scheduled at exactly +60s could be immediately published by WordPress core (when it becomes < MINUTE_IN_SECONDS away), intermittently consuming an extra retry and causing test_request_max_retries to fail.
Changes:
- Add a small scheduling headroom (
SCHEDULE_HEADROOM_SECONDS) and compute scheduled timestamps viatime() + delay + headroomto reliably keep retries infuture. - Simplify
post_date/post_date_gmtcalculation by deriving both from a singlegmdate()value. - Add a deterministic regression test asserting scheduled retries clear the
MINUTE_IN_SECONDSpublish boundary.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| plugins/newspack-plugin/includes/data-events/class-webhooks.php | Adds headroom and clamps minimum delay to prevent “future → publish” boundary flips when scheduling retries. |
| plugins/newspack-plugin/tests/unit-tests/webhooks.php | Adds a regression test covering the publish-boundary timing issue. |
Wrap the new regression test in try/finally so the action-scheduler filter is removed even if an assertion fails, and assert a request exists before indexing it. Document that schedule_request() clamps the delay to a 1-minute minimum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hey @adekbadek, good job getting this PR merged! 🎉 Now, the Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label. If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label. Thank you! ❤️ |
All Submissions:
Changes proposed in this Pull Request:
Fixes the intermittently-failing
Newspack_Test_Webhooks::test_request_max_retries, which was red-lighting unrelated PRs on unlucky CI timing.The failure was a real timing bug, not a test problem. The webhook retry mechanism parks a failed request as a
futureWordPress post and re-processes it when WordPress republishes it. WordPress core (wp_insert_post(),wp-includes/post.php) flips afuturepost straight topublishwhen its date is less thanMINUTE_IN_SECONDS(60s) away.schedule_request()scheduled the shortest backoff at exactlystrtotime( '+1 minutes' )=+60s, sitting right on that edge. When a wall-clock second ticked between computing that timestamp and WordPress' owngmdate()check, the gap became 59s and the post published immediately, firing an extratransition_post_status→process_request→ recorded error. The retry counter (count( $errors )) then hitMAX_RETRIESone iteration early, so the request was trashed before its last attempt.Empirically: replaying the old schedule math flipped to
publish~0.2% of the time; with the fix, 0/3000.Changes:
schedule_request()clamps the delay to a 1-minute minimum and adds a namedSCHEDULE_HEADROOM_SECONDS(5s) buffer, so a scheduled retry reliably clears the publish boundary regardless of sub-second timing.post_dateandpost_date_gmtfrom a singlegmdate()(WordPress forces the PHP timezone to UTC), dropping a no-opstrtotime()round-trip and itsphpcs:ignore.test_scheduled_retry_clears_publish_boundary— a deterministic regression test asserting the scheduled offset clearsMINUTE_IN_SECONDS.Behaviour change is publisher-invisible: retries land ~5s later, never sooner.
Closes # .
How to test the changes in this Pull Request:
plugins/newspack-plugin, run the new regression test:n test-php --filter test_scheduled_retry_clears_publish_boundary(green; it fails onmainwithFailed asserting that 60 is greater than 60).for i in $(seq 1 30); do n test-php --filter test_request_max_retries || break; done(all green).n test-php --filter Newspack_Test_Webhooks(26 tests, 56 assertions).Other information: