Skip to content

fix(health-freshness): revalidate every poll — CF browser-TTL rewrite oscillates synthesized-OK sources to stale (#4910)#4912

Merged
koala73 merged 1 commit into
mainfrom
fix/4910-health-fetch-no-cache
Jul 5, 2026
Merged

fix(health-freshness): revalidate every poll — CF browser-TTL rewrite oscillates synthesized-OK sources to stale (#4910)#4912
koala73 merged 1 commit into
mainfrom
fix/4910-health-fetch-no-cache

Conversation

@koala73

@koala73 koala73 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Fixes #4910. Post-deploy follow-up to #4907 / PR #4908.

Problem

Live probing after #4908 deployed showed the Cloudflare zone's Browser Cache TTL override rewrites the compact health endpoint's Cache-Control: public, max-age=0, must-revalidate to public, max-age=1800, must-revalidate — browsers cache the body for up to 30 minutes. Since PR #4905, healthy checks are synthesized as OK-as-of-checkedAt and compared against the 15-min FRESH_THRESHOLD, so a tab reading a 15–30-min-old cached body computes age > threshold and flips every healthy source badge to stale, then back to fresh when the browser cache window rolls — oscillating ~30-min waves.

(The CDN side works as intended: CDN-Cache-Control: public, s-maxage=60 is respected — cf-cache-status: HIT + x-vercel-cache: HIT observed, origin collapse confirmed.)

Fix

cache: 'no-cache' on the client health fetch: the browser revalidates every poll, Cloudflare answers from its 60s edge cache, so the #4907 origin collapse is intact and checkedAt stays ≤ ~60s + poll interval old — far under threshold. This is also the correct standing declaration for a freshness poller and is robust against future CF browser-TTL config drift.

Verification

  • New failing-first test pins cache: 'no-cache' on the fetch init; 14/14 pass in tests/data-freshness-health.test.mts.
  • npm run typecheck clean; tiered pre-push gate green.

https://claude.ai/code/session_01YKrVoDp8TfEKLYXo83kPFV

… would oscillate synthesized-OK sources to stale (#4910)

Cloudflare's zone Browser-Cache-TTL override rewrites the compact health
endpoint's max-age=0 to 30min (observed live post-#4908). Synthesized-OK
sources compute freshness from checkedAt against the 15-min
FRESH_THRESHOLD, so a browser-cached body 15-30min old flips every
healthy badge to stale, then back on the next cache window — oscillating
~30min waves.

fetch cache: 'no-cache' forces revalidation on every poll; Cloudflare
answers from its 60s edge cache, so the #4907 origin collapse is intact
and checkedAt stays well under threshold.

Fixes #4910

Claude-Session: https://claude.ai/code/session_01YKrVoDp8TfEKLYXo83kPFV
@koala73
koala73 enabled auto-merge (squash) July 5, 2026 17:52
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 5, 2026 5:55pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds cache: 'no-cache' to the browser fetch call inside refreshDataFreshnessFromHealth to force revalidation on every poll, bypassing the Cloudflare Browser-Cache-TTL override that was rewriting the origin's max-age=0 to 30 minutes and causing synthesized-OK source badges to oscillate between fresh and stale.

Confidence Score: 5/5

Safe to merge — a one-line fetch option addition with a direct regression test and no changes to business logic.

The change is minimal and precisely targeted: cache: 'no-cache' added to the fetch init in one file, guarded by a new test that captures the exact RequestInit passed to the poller. The comment ties it to the verified production symptom. No logic paths, data structures, or error handling are altered, and the existing 13-test suite remains intact.

No files require special attention.

Important Files Changed

Filename Overview
src/services/health-freshness.ts Adds cache: 'no-cache' to the health fetch init; one-line change with a well-anchored comment explaining the CF Browser-Cache-TTL interaction. No logic changes elsewhere.
tests/data-freshness-health.test.mts New regression test captures the RequestInit from the injected fetchFn and asserts the cache field is 'no-cache'; correctly calls __resetHealthFreshnessForTests() to clear auth suppression before the probe runs.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Browser
    participant CF_Browser as CF Browser Cache
    participant CF_Edge as CF Edge Cache (60s TTL)
    participant Origin as Origin / Vercel

    Note over Browser,CF_Browser: Before fix — CF rewrites max-age=0 → 1800s
    Browser->>CF_Browser: "GET /api/health?compact=1"
    CF_Browser-->>Browser: Cached body (up to 30 min old)
    Note over Browser: checkedAt age > 15-min FRESH_THRESHOLD<br/>→ all synthesized-OK sources flip to stale

    Note over Browser,Origin: After fix — cache: 'no-cache' on every poll
    Browser->>CF_Browser: "GET /api/health?compact=1 + Cache-Control: no-cache"
    CF_Browser->>CF_Edge: Revalidate (browser cache bypassed)
    alt Edge cache hit (within 60s)
        CF_Edge-->>Browser: Fresh CDN-cached body
    else Edge cache miss
        CF_Edge->>Origin: Fetch from origin
        Origin-->>CF_Edge: "Response + CDN-Cache-Control: s-maxage=60"
        CF_Edge-->>Browser: Response (now cached at edge)
    end
    Note over Browser: checkedAt ≤ ~60s old → synthesized-OK sources stay fresh
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Browser
    participant CF_Browser as CF Browser Cache
    participant CF_Edge as CF Edge Cache (60s TTL)
    participant Origin as Origin / Vercel

    Note over Browser,CF_Browser: Before fix — CF rewrites max-age=0 → 1800s
    Browser->>CF_Browser: "GET /api/health?compact=1"
    CF_Browser-->>Browser: Cached body (up to 30 min old)
    Note over Browser: checkedAt age > 15-min FRESH_THRESHOLD<br/>→ all synthesized-OK sources flip to stale

    Note over Browser,Origin: After fix — cache: 'no-cache' on every poll
    Browser->>CF_Browser: "GET /api/health?compact=1 + Cache-Control: no-cache"
    CF_Browser->>CF_Edge: Revalidate (browser cache bypassed)
    alt Edge cache hit (within 60s)
        CF_Edge-->>Browser: Fresh CDN-cached body
    else Edge cache miss
        CF_Edge->>Origin: Fetch from origin
        Origin-->>CF_Edge: "Response + CDN-Cache-Control: s-maxage=60"
        CF_Edge-->>Browser: Response (now cached at edge)
    end
    Note over Browser: checkedAt ≤ ~60s old → synthesized-OK sources stay fresh
Loading

Reviews (1): Last reviewed commit: "fix(health-freshness): revalidate every ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant