I built cachewhy around a small but costly HTTP cache mistake. Imagine a request for /missing.js that returns 404 with Cache-Control: public, max-age=2678400. That lifetime is 31 days. A browser may reuse the missing response even after the file is deployed. Changing the server header helps new responses, but it does not automatically erase a copy the browser already holds.
The command fetches a URL and explains the response that came back. It prints one row for a private browser cache and one for a shared cache, along with the header that drove the result. It follows redirects with a bound and cancels the response body after reading headers. The local fixture in the repository serves both the long lived 404 and a new response with no-store.
npx --yes github:Arthur031221/cachewhy https://example.com
The report uses http-cache-semantics for storage and freshness calculations, then accounts for Age, Date, and request delay. It distinguishes no-store from no-cache: the former prevents storing a new response, while the latter allows storage but requires validation before reuse. s-maxage changes the shared cache row. An ETag or Last-Modified value can help with revalidation once a response is stale.
A remote probe has important limits. It cannot tell what is already in a particular browser's cache, whether a service worker intercepts the request, which cache key a CDN chose, or whether a provider rule overrides the visible headers. A CDN might serve the probe from its own cache. The output is a reading of the response received now, not a replay of every user's experience.
I included tests for a cached 404, no-store, private responses, s-maxage, Age, stale revalidation, redirects, and timeouts. The fixture makes the main failure visible without depending on a live website. It is useful for debugging a specific URL and for attaching a compact explanation to an issue.
The repository is MIT licensed: https://github.com/Arthur031221/cachewhy. I would value examples of real response headers where the explanation is too terse or reaches beyond what those headers establish.

Top comments (2)
The limit you call out about an existing browser cache is important. A useful companion to the fixture would be a two-stage browser test: request the long-lived 404, switch the same URL to 200/no-store, then compare an ordinary repeat request in that profile with a fresh profile and the CLI probe. Keep service workers out of the fixture and record whether each request reached the origin. That would show why a clean report for the current 200 doesn't establish that a returning user's cached 404 is gone. Does the output already distinguish "this response must not be stored" from any claim about earlier stored responses?
Thanks, that is a useful distinction. The output labels a current
no-storeresponse as not storable, and it does not claim to remove or invalidate a response already stored by a browser. The fixture currently demonstrates the server responses through the CLI, not a two-stage browser test. I would add your same-profile and fresh-profile comparison with origin request logging to show whether the repeat request reaches the server.