You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Moves OneSearch's indexing out of the request and into background jobs on Action Scheduler, and shows each site's indexing status on the governing site.
Re-index runs a job for each site. The governing site's own posts are indexed in batches of 100, and each brand site is asked to run its own job.
The old records stay searchable until a full run is done, and then the ones it didn't save again are deleted.
Clicking Re-index while a run is going starts it over.
Saving, trashing or deleting a post queues a job at the end of the request, instead of syncing in the save.
Each post's result is kept in post meta, either when it was indexed or why it couldn't be. Only errors about the post itself go there, like a post too big to index. A connection or service error fails the site's job with the reason instead.
The Indices and Search screen shows how much of each site is in search, the posts that couldn't be indexed and why, and each site's indexing progress or failure. It checks again every 10 seconds while a job is going.
Removing a brand site, or switching a governing site to a brand site, deletes the jobs about the sites it no longer has.
Uninstall deletes the waiting jobs, the job records and each post's index status. Actions that already ran stay, with their logs.
Why
A re-index ran inside the REST request. The governing site indexed itself and then each brand site one after another, so anything past a small network timed out (around 45 seconds per brand site in QA). It deleted the site's records first, so search was missing that site's posts until it finished, or for good if it timed out. It also said "Re-indexing scheduled successfully." when nothing was scheduled.
Post syncs ran in the save request, so a slow or failing Algolia call slowed down saving the post, and the error only went to the log. Nothing showed which posts were in search or why one wasn't.
inc/Jobs/ is a small jobs layer on Action Scheduler (bundled through Composer), with no OneSearch names so the other One plugins can reuse it. Abstract_Job keeps a record of each job's state in its own option and fails it when it throws or when Action Scheduler cancels, deletes or times out its action. Parent_Job_Trait splits a job's work across child jobs.
Index_Site_Job runs for a site, either all its posts or a list of them, and splits them across Index_Posts_Job children. Index_Status keeps each post's result.
Indexer_Controller replaces Reindex_Controller at /onesearch/v1/indexer. GET returns each site's summary and jobs, and POST starts a re-index. Records get an indexed_at, which the stale cleanup uses.
AI Disclosure
Claude Code to redraft the jobs layer, the indexing jobs and their tests, review them in rounds, and QA them on a local wp-env network against a test Algolia app.
Testing Instructions
Note
Jobs run on WP-Cron, and a Re-index starts the runner right away. To run waiting jobs by hand, use npm run wp-env run cli -- wp action-scheduler run --group=onesearch, or Tools > Scheduled Actions.
Connect a governing site and a brand site, add the Algolia credentials, pick the post types to index and click Save Changes. That starts a re-index.
On the governing site's Indices and Search screen, each site shows "Indexing will start soon.", then its progress, then how many of its items are in search. Search keeps returning the old results while it runs.
Click Re-index while a run is going. The modal says indexing is already running, and re-indexing starts it over.
Edit a post, then trash it. Its records update in Algolia once its onesearch_job_index_site action runs.
Add add_filter( 'onesearch_algolia_record_size_limit', fn() => 50 ); and re-index. The posts show in the site's failed list as too big to index. Remove the filter after.
Set a wrong Algolia write key and re-index. The site shows as failed with the reason, and its posts don't get the error.
Change the brand site's API key so the governing site can't reach it, and reload. The brand site shows the connection error.
Remove the brand site. Its jobs no longer show.
Screenshots
Before
After
Clicking Re-index
Indexing in progress
(no running state, just the end of the click)
Re-index while a run is going
None (the button is disabled while its request runs)
Completed
Posts too big to index
(they drop out of search without a word)
Wrong Algolia key
Brand site unreachable
Additional Info
Brand sites don't have a screen for this yet, so the governing site shows their status.
A followup PR will move the brand config purges and the record deletes of removed sites into jobs too
❌ Patch coverage is 92.75168% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.83%. Comparing base (7ae9441) to head (a091b67).
This is reviewed by Opus on behalf of master Utsav.
Wish I had seen this before I spent time trying to understand the inline feedback.... Is there any review from actual, human @up1512001 ? I too know how to ask 5.5 for a review.
Also, the PR description mentions PUT /indexer, but that isn't in the code.
Leftover from an early code review, PR description updated.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Job-record caching can skip queued updates, and premature completion reporting can hide cleanup failures.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Expose completed status only after stale-record cleanup succeeds
inc/Jobs/Abstract_Job.php:299
This write exposes completed before Index_Site_Job::on_complete() finishes its fallible stale-record cleanup. A status GET during cleanup can stop UI polling, leaving a subsequent failure undisplayed until reload. Keep the persisted status nonterminal until cleanup succeeds, guard against duplicate finalization, and preserve callbacks that delete their own records.
Invalidate notoptions cache when clearing incremental job records
inc/Jobs/Job_Records.php:73
Clearing the individual option leaves WordPress's notoptions cache intact. If another request recreates an incremental job record after the runner deleted it, the runner can still read it as missing and skip the queued post update. Invalidate the negative cache too, and add a regression test for an externally recreated record after a cached miss.
Bundles Action Scheduler and adds a small jobs layer on top of it, with no
OneSearch names in it so the other One plugins can reuse it. Queue wraps
the Action Scheduler calls. Abstract_Job runs one job and keeps a record of
its state for each key it runs for, with the job's status, its one error
and the data it keeps. Job_Records keeps each record in its own option. A
job fails when it throws, and also when Action Scheduler cancels, deletes
or times out its action, or PHP dies under it. on_complete() and
on_failure() run when it finishes. Job_Registry builds the jobs of one
Action Scheduler group from their names and reports their statuses.
Nothing registers a job yet. Uninstall deletes the actions still waiting
to run and the job records, and keeps the actions that already ran along
with their logs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-index and post edits now index in the background instead of inside the
request. POST /indexer replaces the old re-index route and runs an
Index_Site_Job for each site, the governing site included, then starts
Action Scheduler's runner so the admin watching doesn't wait for WP-Cron.
For this site Index_Site_Job splits the posts across Index_Posts_Job
children of 100 posts each. They save or delete their posts and report back
how many were done and how many failed. The old records stay searchable
until a full run is done, and then it deletes the ones it didn't save
again. For a brand site it asks the brand site to run its own
Index_Site_Job. Re-index while a run is going starts it over, and the old
run's waiting children stop. The parent side lives in Parent_Job_Trait in
the jobs layer, which finishes the parent once every child of its run has
finished.
Post edits run Index_Site_Job for this site with the posts that changed, at
the end of the request. That run has its own record, so a post edit never
replaces a re-index.
Index_Status keeps each post's result in post meta, when it was indexed or
why it couldn't be, and counts a summary of each site from it. Only errors
about the post itself go there, like a post too big to index. A connection
or service error fails the site's Index_Site_Job with the reason, and a
post edit that can't reach Algolia marks the site's own run failed, so the
next re-index tries again. GET /indexer returns every site's summary and
jobs, with each brand site's report fetched from it.
The settings screen shows each site's summary and jobs, and checks on them
while a job is going.
Removing a brand site, or switching a governing site to a brand site,
deletes the job records about those sites. A brand site that its governing
site removes forgets which posts were in search. Config purges and the
record deletes of removed sites still work as before. Uninstall also
deletes each post's index status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Moves OneSearch's indexing out of the request and into background jobs on Action Scheduler, and shows each site's indexing status on the governing site.
Why
A re-index ran inside the REST request. The governing site indexed itself and then each brand site one after another, so anything past a small network timed out (around 45 seconds per brand site in QA). It deleted the site's records first, so search was missing that site's posts until it finished, or for good if it timed out. It also said "Re-indexing scheduled successfully." when nothing was scheduled.
Post syncs ran in the save request, so a slow or failing Algolia call slowed down saving the post, and the error only went to the log. Nothing showed which posts were in search or why one wasn't.
Related Issue(s):
How
inc/Jobs/is a small jobs layer on Action Scheduler (bundled through Composer), with no OneSearch names so the other One plugins can reuse it.Abstract_Jobkeeps a record of each job's state in its own option and fails it when it throws or when Action Scheduler cancels, deletes or times out its action.Parent_Job_Traitsplits a job's work across child jobs.Index_Site_Jobruns for a site, either all its posts or a list of them, and splits them acrossIndex_Posts_Jobchildren.Index_Statuskeeps each post's result.Indexer_ControllerreplacesReindex_Controllerat/onesearch/v1/indexer. GET returns each site's summary and jobs, and POST starts a re-index. Records get anindexed_at, which the stale cleanup uses.AI Disclosure
Claude Code to redraft the jobs layer, the indexing jobs and their tests, review them in rounds, and QA them on a local wp-env network against a test Algolia app.
Testing Instructions
Note
Jobs run on WP-Cron, and a Re-index starts the runner right away. To run waiting jobs by hand, use
npm run wp-env run cli -- wp action-scheduler run --group=onesearch, or Tools > Scheduled Actions.onesearch_job_index_siteaction runs.add_filter( 'onesearch_algolia_record_size_limit', fn() => 50 );and re-index. The posts show in the site's failed list as too big to index. Remove the filter after.Screenshots
Additional Info
Checklist