Shopify / Shopify/hydrogen

Allow applications to control logging for SWR background revalidation failures

Open
#4,039 0 comments 0 reactions 0 assignees View on GitHub

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 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

  1. Install @shopify/hydrogen@2026.4.3.
  2. Save the script below as repro.mjs.
  3. 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: () => 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.