Skip to content

fix: honor stale-if-error on connection errors during revalidation#5513

Open
jeswr wants to merge 2 commits into
nodejs:mainfrom
jeswr:fix/cache-stale-if-error-network
Open

fix: honor stale-if-error on connection errors during revalidation#5513
jeswr wants to merge 2 commits into
nodejs:mainfrom
jeswr:fix/cache-stale-if-error-network

Conversation

@jeswr

@jeswr jeswr commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #5507

What

stale-if-error (response and request directive) was only honored when the origin responded with a 500-504 status code. If the origin was unreachable (connection refused, reset, etc.), CacheRevalidationHandler.onResponseError failed the request outright even when a stale entry within the stale-if-error window existed.

RFC 5861 §4 defines an error as "any situation that would result in a 500, 502, 503 or 504 HTTP response status code being returned" — for a client-side cache, failing to reach the origin is exactly the situation a gateway would surface as 502/504, and resilience across origin outages is the extension's headline use case (nginx proxy_cache_use_stale error, Varnish and Fastly all treat connect errors this way).

Fix

The interceptor already computes the within-threshold decision (withinStaleIfErrorThreshold in lib/interceptor/cache.js) and passes it to the revalidation handler as allowErrorStatusCodes. This change also uses it on the error path: in onResponseError, when the response hasn't started yet (the callback is still pending) and we're within the threshold, invoke the callback with success so the stale entry is served. Errors after a response has started, or outside the window, propagate exactly as before.

Repro / evidence

Store a response with Cache-Control: max-age=1, stale-if-error=60; stop the origin; wait past freshness; request again (undici main @ cb4c2f1, Node 22):

  • Before: throws ECONNREFUSED (the equivalent scenario with the origin returning 503 already served stale, making the inconsistency surprising)
  • After: stale cached response served; once outside the stale-if-error window the connection error is propagated again

Added tests for both the response directive and the request directive variants in test/interceptors/cache.js ("stale-if-error (response) on connection error", "stale-if-error on connection error"); both fail on main and pass with this change.

Cache test suite (test/interceptors/cache*.js, test/cache-interceptor/*): 123 pass / 0 fail (baseline 121 pass + the 2 new tests). mnot cache-tests conformance runner (node test/cache-interceptor/cache-tests.mjs): the stale-sie-close check ("Does HTTP cache serve stale stored response when server sends Cache-Control: stale-if-error and subsequently closes the connection?") now answers yes in all environments (previously no) — summary went from 220 passed / 58 failed-optional to 222 passed / 56 failed-optional, with 0 required failures and no regressions. No skip-list entries relate to this path (stale-close-must-revalidate/stale-close-no-cache depend on plain-close behavior without stale-if-error, which is unchanged).

Notes

  • Related PRs from the same review, touching adjacent cache-revalidation logic (each self-contained against main): jeswr:fix/cache-must-revalidate-max-stale, jeswr:fix/cache-if-modified-since-value.
  • This change is agent-assisted (Claude Fable 5) working with @jeswr.

🤖 Generated with Claude Code

stale-if-error was only honored when the origin responded with a
500-504 status code; if the origin was unreachable (e.g. ECONNREFUSED),
CacheRevalidationHandler.onResponseError failed the request outright
even when a stale entry within the stale-if-error window existed.

RFC 5861 section 4 defines an error as "any situation that would result
in a 500, 502, 503 or 504 HTTP response status code being returned" -
for a client-side cache, failing to reach the origin is exactly the
situation a gateway would surface as 502/504.

Repro (undici main, Node 22): store a response with
'Cache-Control: max-age=1, stale-if-error=60', stop the origin, wait
past freshness, request again.
- Before: throws ECONNREFUSED
- After: stale cached response served (and the error still propagates
  once outside the stale-if-error window)

The interceptor already computes the within-threshold decision
(withinStaleIfErrorThreshold) and passes it to the handler; this change
uses it on the error path too, when no response has started, invoking
the callback with success so the stale entry is served.

The mnot cache-tests conformance check 'stale-sie-close' now answers
yes in all environments (previously no); no required tests regressed.

Fixes nodejs#5507

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 16:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov-commenter

codecov-commenter commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.44%. Comparing base (cb4c2f1) to head (52c2709).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5513   +/-   ##
=======================================
  Coverage   93.44%   93.44%           
=======================================
  Files         110      110           
  Lines       37328    37338   +10     
=======================================
+ Hits        34881    34891   +10     
  Misses       2447     2447           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Addresses review feedback by trimming the stale-if-error comments to
terse one-liners in line with the surrounding interceptor style;
comment-only, no logic change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeswr jeswr force-pushed the fix/cache-stale-if-error-network branch from 2fff5f1 to 52c2709 Compare July 4, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cache: stale-if-error only honored for 500-504 responses, not connection/network errors (RFC 5861 §4)

3 participants