Skip to content

Core Data: Cache-bust refetches of an already received resolution - #81938

Draft
adamsilverstein wants to merge 2 commits into
fix/entity-records-stale-list-responsefrom
add/cache-bust-invalidation-refetches
Draft

adamsilverstein wants to merge 2 commits into
fix/entity-records-stale-list-responsefrom
add/cache-bust-invalidation-refetches

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Aug 21, 2026

Copy link
Copy Markdown
Member

Test this PR in WordPress Playground

Another round with Claude on #81844, this time chasing the cache angle:

See #81844 - the response-ordering guards in #81846/#81886 close a real race, but the behavior reported there (stale media_details.sizes served consistently, surviving a reload) points at the refetch itself being served stale data. This is the client-side hardening for that case.

Note

Stacked on #81886 (in turn stacked on #81846) - only the last commit here is new.

What?

getEntityRecord and getEntityRecords now append a cache-busting argument to a request made after the same resolution has already received a response. First requests are untouched and stay cacheable.

Why?

A resolver only re-runs for a resolution it has already resolved after an invalidation - which means the data is believed to have changed on the server. Some hosts cache REST responses keyed on the full request URL, even authenticated ones WordPress marks no-cache. Such a cache serves an invalidation-triggered refetch the very response the invalidation is trying to replace, because the refetch uses the exact same URL as the request that populated the cache.

Client-side media uploads are where this bites. The attachment record is fetched while sub-sizes are still being generated - media_details.sizes is empty until the finalize request writes them - and the post-upload refetch asks for the same URL. Served the stale copy, the store keeps an attachment without sizes, the Image block's Resolution control never appears, and nothing invalidates it afterwards. The ordering guards cannot help here: the newest response is the stale one.

Worth noting the limit up front: this protects refetches only. A fresh editor load's first request still uses the plain URL, so a cache entry poisoned mid-upload can serve it until the entry expires. That part is not fixable client-side - caching authenticated REST responses against their no-cache headers is a hosting misconfiguration - but this change keeps the current session correct instead of leaving the stale copy in charge of it.

How?

The lastReceivedResponse bookkeeping added in #81846 already records, per registry and resolution, that a response has been received. A request whose resolution is already recorded there is a refetch, and gets _cacheBust=<timestamp>-<sequence> appended - to the request path only, never to the query used for store keys, so resolution state and stable keys are unchanged. The REST API ignores unknown parameters.

  • One buster per resolver run covers all three getEntityRecords fetch branches; the pages of a progressive run share it.
  • A failed first request was never "received", so its retry is not busted - the same conservative line the ordering guard draws.
  • The value combines a timestamp with the claimed sequence number, so two sessions - or two refetches in the same millisecond - never share a URL.

Testing Instructions

All tests were verified to fail without the change and pass with it.

Unit - refetches are busted, first and unrelated requests are not:

npm run test:unit -- --testPathPatterns='packages/core-data/src/test/resolvers.js'

E2E - simulates a full-URL-keyed cache in front of wp-json (every attachment GET is served from a cache once a response for that exact URL has been seen) and uploads through the real client-side pipeline:

npm run wp-env-test start
npm run test:e2e -- test/e2e/specs/editor/various/client-side-media-processing.spec.js --project=chromium -g "URL-keyed cache"

Manual: insert an Image block, upload a JPEG, and watch the Network tab filtered on /wp/v2/media. The attachment GET issued mid-upload uses the plain URL; the refetch after the upload completes carries _cacheBust=.... The Resolution control appears in the block's Settings panel once the refetch lands.

AI Use

Code and description written with 🤖 Claude Code. I will review and test.

A resolver re-runs for a resolution it has already resolved only after
an invalidation, which means the data is believed to have changed on
the server. Some hosts cache REST responses keyed on the full request
URL, even authenticated ones WordPress marks no-cache, and serve such a
refetch the very response the invalidation is trying to replace: a
client-side media upload refetches the attachment record with the same
URL it requested before the sub-sizes were generated, so the Image
block's Resolution control never appears. Append a cache-busting
argument to these refetches so they reach the server. First requests
are left untouched and stay cacheable.

See #81844.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions github-actions Bot added the [Package] Core data /packages/core-data label Aug 21, 2026
@github-actions

Copy link
Copy Markdown

Size Change: +81 B (0%)

Total Size: 7.75 MB

📦 View Changed
Filename Size Change
build/scripts/core-data/index.min.js 37.5 kB +81 B (+0.22%)

compressed-size-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Core data /packages/core-data

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant