Survive the 503 that made a healthy deployment look like an outage - #73
Merged
Conversation
A user reported the app was down. It was not: all 53 framework assets serve 200 and every SHA-256 matches the boot manifest byte for byte, the failing one included. What happened is that GitHub Pages answered one file with a passing 503, the browser hashed the error page instead of the assembly, the integrity check failed, and Blazor stopped for good. There was nothing to see. The failure happens before Blazor owns an error UI, so the visitor gets the loading circle and no message — which is why the report arrived as "the app is down" rather than "System.IO.Compression did not arrive". The same lesson as the version in the desktop footer: a user can only report what the app is willing to tell them. So boot.js retries a boot file three times before believing it, and explains itself in both languages if the retry does not help, naming the file that never came. It keeps the integrity guarantee rather than trading it away: returning a Response from loadBootResource takes the check away from Blazor, so the manifest hash goes to fetch instead and the browser enforces it. Verified by reproduction, not by argument. Against a server that 503s that same asset twice, the console prints the reporter's three lines verbatim and the app now boots anyway. Against one that never yields, the panel appears in English and in Spanish. Blazor.start() does not reject when a boot file will not come — the failure surfaces as an unhandled rejection inside mono_download_assets — so the explanation is written from the retry loop, which is what knows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015PEbbiYSNPw7jE3LrPNhyF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A user reported the app was down. It was not.
What was actually wrong
All 53 framework assets serve
200and every SHA-256 matches the boot manifest byte for byte — the file in the report included:sha256-gx2VPgq3h4wLjW7iWPZwsBNKDDRcGzmOad/1nUBl9nE=gx2VPgq3h4wLjW7iWPZwsBNKDDRcGzmOad/1nUBl9nE=GitHub Pages answered one file with a passing
503. The browser hashed the error page instead of the assembly, gotJ5J7M+…, SRI blocked it, and Blazor abandoned startup for good.One blip on any of fifty-three files bricked the load. And there was nothing to see: the failure happens before Blazor owns an error UI, so the visitor gets the loading circle and no message. That is why the report arrived as "the app is down" rather than "
System.IO.Compressiondid not arrive" — the same lesson as the version in the desktop footer. A user can only report what the app is willing to tell them.What this changes
wwwroot/js/boot.js, plusautostart="false"so it can start Blazor itself:cache: 'reload', since asking the cache again for a body we already know is bad is useless.The integrity guarantee is kept, not traded away. Returning a
ResponsefromloadBootResourcetakes the check away from Blazor, so the manifest hash is handed tofetchand the browser enforces it instead. The runtime's own ES modules are left to the default loader — answering those with aResponsebreaks the import.Verified by reproduction, not by argument
Against a local server that 503s that same asset, in a real browser:
System.IO.Compression….wasm, EN and ESOne finding worth keeping:
Blazor.start()does not reject when a boot file will not come — the failure surfaces as an unhandled rejection insidemono_download_assets. My first attempt hung the panel off a.catchthat never runs; it was caught by testing the permanent-failure case, and the explanation is now written from the retry loop, which is what knows it is out of tries.Tests
410 green. Seven new in
WebBootTests, guarding only what breaks silently: script order (Blazormust exist beforeboot.jsruns), thatintegrityis still handed tofetch(dropping it would fail nothing and quietly stop verifying every assembly), that the runtime modules stay out of the interceptor, and that the panel still speaks both languages.Scope
Web only. The desktop host has its own
index.htmland loads from local disk, where there is no 503 to survive.🤖 Generated with Claude Code
https://claude.ai/code/session_015PEbbiYSNPw7jE3LrPNhyF