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
main and the 2026.4.5 tagged implementation
- Node.js: 24.x
NODE_ENV=production
- Public APIs:
createStorefrontClient, InMemoryCache, and CacheCustom
- 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:
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: () => false does not intercept this path.
- A Cache wrapper cannot observe the background action exception.
- A
waitUntil wrapper receives the already-handled promise.
- Setting
staleWhileRevalidate: 0 changes caching semantics.
- Globally patching
console.error is 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.
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.3mainand the2026.4.5tagged implementationNODE_ENV=productioncreateStorefrontClient,InMemoryCache, andCacheCustomDescription
When a cached Hydrogen subrequest becomes stale,
runWithCachereturns 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 toconsole.error.The existing
logErrorsoption 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
@shopify/hydrogen@2026.4.3.repro.mjs.Minimal reproduction
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:
These are examples rather than a prescribed implementation.
Actual Behavior
The stale response resolves correctly, but Hydrogen internally catches the background exception and invokes:
The console receives:
Why
logErrorsdoes not cover itlogErrorsis evaluated by the GraphQL result wrapper when a resolved result contains anerrorsarray. The SWR failure occurs earlier insiderunWithCache, where the exception is caught and logged directly. It never reaches thelogErrorscallback.Workarounds considered
logErrors: () => falsedoes not intercept this path.waitUntilwrapper receives the already-handled promise.staleWhileRevalidate: 0changes caching semantics.console.erroris overly broad.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.