Allow applications to control logging for SWR background revalidation failures
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.1k
- Forks
- 443
- Avg merge
- 4d 19h
- Merged PRs (30d)
- 24
Description
What is the location of your example repository?
No response
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
2026.4.3
What version of React Router 7 are you using?
No response
Steps to Reproduce
Environment
@shopify/hydrogen:2026.4.3- Also present in current
mainand the2026.4.5tagged implementation - Node.js: 24.x
NODE_ENV=production- Public APIs:
createStorefrontClient,InMemoryCache, andCacheCustom - No live Shopify request is required
Description
When a cached Hydrogen subrequest becomes stale, runWithCache returns the stale value and starts background revalidation.
If that background action throws, Hydrogen catches the exception internally, prefixes its message with SWR in sub-request failed:, and passes the raw error directly to console.error.
The existing logErrors option does not govern this path. Applications that use a bounded or allowlisted production diagnostic policy therefore have no supported way to control the logging of SWR background failures while preserving stale-while-revalidate behavior.
Steps to reproduce
- Install
@shopify/hydrogen@2026.4.3. - Save the script below as
repro.mjs. - Run:
NODE_ENV=production node repro.mjs
Minimal reproduction
import assert from 'node:assert/strict';
import {
CacheCustom,
InMemoryCache,
createStorefrontClient,
} from '@shopify/hydrogen';
const sentinel = 'PRIVATE_PROVIDER_BODY_SENTINEL';
const pending = [];
const consoleCalls = [];
let fetchCount = 0;
const originalFetch = globalThis.fetch;
const originalConsoleError = console.error;
globalThis.fetch = async () => {
fetchCount += 1;
if (fetchCount === 1) {
return new Response(
JSON.stringify({data: {shop: {name: 'cached'}}}),
{
status: 200,
headers: {'content-type': 'application/json'},
},
);
}
throw new Error(sentinel);
};
// Test instrumentation only; this is not proposed as a workaround.
console.error = (...args) => consoleCalls.push(args);
try {
const {storefront} = createStorefrontClient({
cache: new InMemoryCache(),
waitUntil: (promise) => pending.push(promise),
storeDomain: 'example.myshopify.com',
storefrontApiVersion: '2026-04',
publicStorefrontToken: 'unused-by-mocked-fetch',
logErrors: () => false,
});
const query = 'query Repro { shop { name } }';
const cache = CacheCustom({
mode: 'public',
maxAge: 0,
staleWhileRevalidate: 60,
});
const first = await storefront.query(query, {cache});
await Promise.all(pending.splice(0));
// Ensure the maxAge=0 entry is stale.
await new Promise((resolve) => setTimeout(resolve, 20));
const stale = await storefront.query(query, {cache});
await Promise.all(pending.splice(0));
assert.equal(first.shop.name, 'cached');
assert.equal(stale.shop.name, 'cached');
assert.equal(fetchCount, 2);
const rendered = consoleCalls.flat().map(String).join('\n');
assert.match(rendered, /SWR in sub-request failed/);
assert.match(rendered, new RegExp(sentinel));
console.log({
staleValue: stale.shop.name,
fetchCount,
rendered,
});
} finally {
globalThis.fetch = originalFetch;
console.error = originalConsoleError;
}
Expected Behavior
The stale response should remain available, and applications should have a supported mechanism to control or report the background revalidation failure without Hydrogen directly logging the raw thrown value.
Possible API directions could include:
- A background/SWR error callback
- Integration with the existing error logging hook
- Bounded logger injection
- A supported suppression/reporting option
These are examples rather than a prescribed implementation.
Actual Behavior
The stale response resolves correctly, but Hydrogen internally catches the background exception and invokes:
console.error(error);
The console receives:
Error: SWR in sub-request failed: PRIVATE_PROVIDER_BODY_SENTINEL
Why logErrors does not cover it
logErrors is evaluated by the GraphQL result wrapper when a resolved result contains an errors array. The SWR failure occurs earlier inside runWithCache, where the exception is caught and logged directly. It never reaches the logErrors callback.
Workarounds considered
logErrors: () => falsedoes not intercept this path.- A Cache wrapper cannot observe the background action exception.
- A
waitUntilwrapper receives the already-handled promise. - Setting
staleWhileRevalidate: 0changes caching semantics. - Globally patching
console.erroris overly broad. - Patching or forking Hydrogen creates an unsupported maintenance burden.
Impact
A provider, runtime, or network exception may contain content an application does not intend to place in production logs. The unconditional raw log prevents applications from enforcing a strict allowlisted diagnostic policy for this SWR path.
This report does not claim that real credentials or secrets have been observed.
Requested capability
Please provide a supported way for applications to control, suppress, or safely report SWR background revalidation failures while retaining normal stale-cache behavior.
Potential API directions could include a background/SWR error callback, integration with the existing error logging hook, bounded logger injection, or a supported suppression/reporting option. These are examples rather than a prescribed implementation.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read the runWithCache background revalidation path and the logErrors handling used by createStorefrontClient; the report says they currently handle failures differently. Run repro.mjs with the listed Hydrogen APIs to confirm the stale value and logging behavior. Done means a supported application-controlled reporting or suppression mechanism exists without changing stale-while-revalidate behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend, observability
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100