fix(webapp): recover from stale /build assets via a bounded reload#4282
Conversation
On a /build asset or chunk load failure, do a bounded full document reload (max 2 per 5 min) so the page lands on a single consistent build after a rolling deploy. Replaces the reverted #4260 recovery with a minimal reload-only approach: no fetch interception, no build-version polling, no form snapshot, no blocking overlay.
Serve the current build id at /build-version so an already-deployed client build that polls it after a /build asset 404 recovers in a single reload instead of a manual-reload dead-end. Endpoint only — deliberately no X-Build-Id response header. Temporary; safe to remove once older clients have churned out.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📜 Recent review details⏰ Context from checks skipped due to timeout. (18)
🧰 Additional context used📓 Path-based instructions (11)**/*.{ts,tsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
{packages/core,apps/webapp}/**/*.{ts,tsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.{test,spec}.{ts,tsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.ts📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
Files:
apps/webapp/**/*.{ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Files:
apps/webapp/**/*.test.{ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Files:
apps/**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
apps/webapp/app/**/*.{ts,tsx}📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Files:
apps/webapp/app/**/*.ts📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Files:
apps/webapp/**/*.{test,spec}.{ts,tsx}📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Files:
🧠 Learnings (20)📚 Learning: 2026-05-14T14:54:39.095ZApplied to files:
📚 Learning: 2026-03-22T13:26:12.060ZApplied to files:
📚 Learning: 2026-03-22T19:24:14.403ZApplied to files:
📚 Learning: 2026-05-18T08:21:27.694ZApplied to files:
📚 Learning: 2026-05-18T08:21:27.694ZApplied to files:
📚 Learning: 2026-06-13T19:53:13.759ZApplied to files:
📚 Learning: 2026-06-17T17:13:49.929ZApplied to files:
📚 Learning: 2026-06-23T13:04:21.413ZApplied to files:
📚 Learning: 2026-05-12T21:04:05.815ZApplied to files:
📚 Learning: 2026-06-25T18:21:51.905ZApplied to files:
📚 Learning: 2026-07-03T17:10:21.498ZApplied to files:
📚 Learning: 2026-05-18T14:40:02.173ZApplied to files:
📚 Learning: 2026-06-04T18:16:35.386ZApplied to files:
📚 Learning: 2026-06-09T17:58:04.699ZApplied to files:
📚 Learning: 2026-06-16T09:19:47.637ZApplied to files:
📚 Learning: 2026-02-11T16:37:32.429ZApplied to files:
📚 Learning: 2026-05-08T21:00:20.973ZApplied to files:
📚 Learning: 2026-06-25T18:21:55.847ZApplied to files:
📚 Learning: 2026-06-25T18:21:54.729ZApplied to files:
📚 Learning: 2026-04-16T14:21:15.229ZApplied to files:
🪛 ast-grep (0.44.1)apps/webapp/app/components/StaleAssetRecovery.tsx[warning] 91-91: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks. (react-unsafe-html-injection) 🔇 Additional comments (10)
WalkthroughAdds production stale-asset recovery that detects failed build resources and chunk-load errors, then performs bounded page reloads with offline and session-storage safeguards. The recovery script is injected into normal and error document shells. HTML responses default to 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Problem
The webapp's HTML references content-hashed
/buildassets, and each runninginstance contains exactly one build and returns 404 for asset hashes it doesn't
have. During a rolling deploy a client can hold HTML from one build while a
request for one of its assets is served by an instance on a different build →
missing styles or a failed chunk load.
What this does
On a
/buildstylesheet/script/chunk load failure, the client does a boundedfull-document reload (at most 2 per 5 minutes, tracked in
sessionStorage) sothe page reloads onto a single consistent build. That's the whole mechanism — no
polling, no
fetchinterception, no blocking overlay, no form snapshotting.apps/webapp/app/components/StaleAssetRecovery.tsx— authored as a typed,lint-checked function and serialized to an inline script via
.toString()(sothe logic is real, reviewable code, not an opaque string), injected before
<Links />, production only.errorlistener for<link>/<script>/modulepreloadfailures under
/build/, plus anunhandledrejectionguard for dynamic-importfailures.
navigator.onLinecheck so it never reloads into an offline error page.StaleAssetRecovery.test.ts.Relationship to #4260
Replaces the recovery introduced in #4260 (reverted in #4280) with a much
smaller, reload-only approach — the previous version intercepted
fetchandcould turn a data request into a navigation, and showed a full-screen overlay on
any asset error; this drops both.
/build-versioncompatibility shimapps/webapp/server.tsadds a tinyGET /build-versionendpoint (build id only,no-store). A previously-deployed client build polls it after an asset failureand reloads once it sees a newer build, so those older tabs recover in one reload
instead of getting stuck. Temporary — safe to remove once older clients have
cycled out. It deliberately does not re-add an
X-Build-Idresponse header.Also
Restores the
.server-changeswriting guidance in.claude/rules/server-apps.md(reverted alongside #4260).
Self-hosting note
Recovery is most reliable when your load balancer keeps a client on one instance
for the duration of a deploy (short session stickiness) — the reload then lands
on a consistent build in one hop.