fix: max-age=0 and no-cache must be considered "immediately stale"#4624
Open
steunix wants to merge 1 commit into
Open
fix: max-age=0 and no-cache must be considered "immediately stale"#4624steunix wants to merge 1 commit into
steunix wants to merge 1 commit into
Conversation
As per discussion nodejs#4620 (reply in thread) Returning undefined causes the caller to ignore the result from a caching standpoint: max-age=0 and no-cache should return 0 for "immediately stale".
Author
Sorry but I don't know the code base enough to even guess at what level to put a test. |
Uzlopak
reviewed
Oct 16, 2025
Uzlopak
left a comment
Contributor
There was a problem hiding this comment.
Lets see if the tests are still happy fist
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4624 +/- ##
==========================================
- Coverage 92.93% 92.92% -0.01%
==========================================
Files 106 106
Lines 33092 33097 +5
==========================================
+ Hits 30753 30756 +3
- Misses 2339 2341 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Member
|
You can put it within the interceptors tests for cache interceptor, attempting to just replicate the behavior described in the issue |
7 tasks
jeswr
pushed a commit
to jeswr/undici
that referenced
this pull request
Jul 4, 2026
A 304 validation response on the synchronous revalidation path was swallowed by CacheRevalidationHandler before the CacheHandler could see it, so the stored entry was never updated per RFC 9111 §4.3.4: headers carried by the 304 (e.g. a new Cache-Control) were discarded and the entry's freshness was never recomputed, causing a revalidation request on every single subsequent use, forever. The served response also kept the obsolete `Warning: 110` header even though it had just been successfully validated. - CacheHandler now handles 304s before the cacheability gates and recomputes freshness from the merged (stored + 304) headers, so bare 304s also refresh the entry; the stored Vary and ETag are preserved. - CacheRevalidationHandler forwards 304s to a dedicated cache-update CacheHandler and passes the validation response's headers to the callback, so the interceptor serves the freshened headers with age 0 and no stale warning. - The 304 body-reuse path no longer byte-iterates Buffer bodies returned by SqliteCacheStore (Buffer.prototype.values() yields single bytes). Un-skips four 304-etag-update-response-* conformance tests; 304-etag-update-response-Cache-Control remains skipped as it needs an entry-retention floor (the max-age=1 entry is deleted before the test's 3s pause; see nodejs#4624). Fixes nodejs#5505 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mcollina
pushed a commit
that referenced
this pull request
Jul 16, 2026
#5515) * fix: store revalidation-only responses so etag revalidation can engage Responses with a validator (ETag or Last-Modified) but zero freshness lifetime - `cache-control: no-cache` or `max-age=0` without any other freshness source - were never stored: determineStaleAt() returned undefined, so the conditional-request flow never engaged and every request was answered by a full 200 from the origin. This is the canonical "cache but always validate" pattern for API servers, and RFC 9111 (sections 3 and 5.2.2.4) explicitly allows storing these responses as long as each reuse is revalidated. Repro (undici main, Node 22): origin serving `cache-control: no-cache` + `etag` (no Last-Modified): before: 2 requests -> 2 full 200s, no if-none-match sent after: 2 requests -> 1 full 200 + 1 conditional revalidation answered with a 304, cached body reused Same for `cache-control: max-age=0` + `etag`. The already-working no-cache + Last-Modified case (stored via the heuristic-freshness branch) is unchanged and covered by a new regression test. The fix stores such responses with immediate-stale semantics: - determineStaleAt() returns 0 (instead of undefined) for max-age=0 / s-maxage=0 / unqualified no-cache when the response has a usable validator; - the "response is already stale" rejection in onResponseStart() is relaxed for these revalidation-only entries; - determineDeleteAt() retains them for a bounded 24h window (the usual buffer is proportional to the freshness lifetime, which is zero here); every successful revalidation re-stores the entry, sliding the window. The read side needs no changes: stored no-cache entries are already forced through revalidation by needsRevalidation(), and max-age=0 entries are always stale, so isStale() triggers the same conditional flow. Also makes the previously failing (optional) mnot cache-tests conformance test cc-resp-no-cache-revalidate pass in all environments (220 -> 221 passed, 58 -> 57 failed-optional, 0 required failures). Supersedes #4624, relates to discussion #4620. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: shorten cache interceptor comments Collapse the multi-sentence comments introduced by this PR to the terse, single-line style used elsewhere in lib/interceptor, keeping the RFC 9111 references. Addresses proactive review feedback on comment terseness. No logic change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jesse Wright <jw896977@gmail.com> Co-authored-by: Claude Fable 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
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.
This relates to...
#4620 (reply in thread)
Rationale
When server is responding with a max-age=0 or no-cache, Undici does not cache the response and subsequent requests do not include if-none-match or if-modified-since headers.
Changes
The function determineStaleAt does not take max-age=0 and no-cache into account, returning undefined in both cases: the caller will ignore the response in regard to the cache handling
Features
N/A
Bug Fixes
N/A
Breaking Changes and Deprecations
N/A
Status