fix: [#2373] ResponseCache must not evict responses revalidatable via Last-Modified - #2266
Open
marciomazza wants to merge 1 commit into
Open
Conversation
This was referenced Aug 2, 2026
Open
marciomazza
force-pushed
the
fix/response-cache-lastmodified-eviction
branch
from
September 4, 2026 20:16
0d01bfe to
3c2a3ee
Compare
ResponseCache must not evict responses revalidatable via Last-Modified
marciomazza
force-pushed
the
fix/response-cache-lastmodified-eviction
branch
3 times, most recently
from
September 8, 2026 14:19
655905c to
4e560b9
Compare
…t-Modified add()'s expiry cleanup deleted any cache entry without an ETag as soon as it was expired, ignoring Last-Modified entirely - unlike get(), which keeps an expired entry with a Last-Modified date around as "stale" so it can be revalidated with a conditional If-Modified-Since request. This mismatch meant a response with a very small/zero max-age plus a Last-Modified header (a common "always revalidate" pattern) was purged from the cache immediately instead of being kept for revalidation, causing the next request to be an ordinary uncached GET instead of a conditional one. This also caused the intermittent SyncFetch.test.ts cache-revalidation failures on master. Adds deterministic regression tests in Fetch.test.ts and SyncFetch.test.ts: an already-expired Last-Modified response with no ETag or Cache-Control must stay cached so the next request is a conditional If-Modified-Since GET.
marciomazza
force-pushed
the
fix/response-cache-lastmodified-eviction
branch
from
September 8, 2026 14:27
4e560b9 to
4f36c23
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Description
Resolves #2373
ResponseCache.get()keeps an expired entry that has aLast-Modifieddate asstaleso the next request can revalidate it withIf-Modified-Since.ResponseCache.add()didn't: its eviction check only looked atETag, so aresponse with
Cache-Control: max-age=0+Last-Modifiedand noETag(acommon "always revalidate" setup) was purged immediately, turning every
follow-up request into an uncached
GETinstead of a conditional one.Root cause:
add()'s expiry cleanup dropped any expired entry without anETag, ignoringlastModified— out of sync with the check inget().Fix: add the same
!cachedResponse.lastModifiedterm toadd()'s evictioncondition, so an entry is only dropped when it has neither validator.
Tests
Added 2 tests (
test/fetch/Fetch.test.ts,test/fetch/SyncFetch.test.ts): analready-expired
Last-Modifiedresponse with noETagand noCache-Controlmust stay cached so the next request is a conditional
If-Modified-SinceGET,not a plain refetch. Both fail on
master(the second request goes outunconditional) and pass with this change. Full
npm testpasses.Verification
The new tests use an
Expiresdate in the past rather than a sub-millisecondmax-age, so they fail deterministically onmasterinstead of only underscheduling latency — this same eviction is the cause of the intermittent
SyncFetch.test.ts/Fetch.test.tscache-revalidation failures onmaster.npm test(happy-dom package) — 302 files, 7721 tests green.Behavior checked against HTTP caching semantics (RFC 9111 §4.3): a stored
response with a validator must be revalidated on expiry, not discarded.
AI
I used Claude Code to write this, over multiple iterations. I guided and reviewed
it myself at every step.
Before submitting the PR, please make sure you do the following:
Tests
Fetch.test.ts,SyncFetch.test.ts). Not runnable in a browser console (server-side response cache); verified against RFC 9111 §4.3 conditional-request semantics instead.npm testlocally to make sure that all tests pass before submitting the PR.Title
feat,fix,choreorBREAKING CHANGE. The issue is optional and can be omitted if the pull request does not relate to an issue.AI tools