dotnet/runtime · error · Error
Failed to load resource
Error message
Failed to load resource '${asset.name}' from '${asset.resolvedUrl}' after multiple attempts. Last HTTP status: ${response.status} ${response.statusText} What it means
Final failure from loadResourceRetry after three attempts (initial + two retries) all returned non-ok responses on a retryable status code. Retry is bypassed only for noRetryStatusCodes (400,401,403,404,405,406,409,410,411,413,414,415,422,426,501,505) and for dotnetwasm/symbols behaviors. Thrown only when enableDownloadRetry is true (default).
Solutions
- Check whether the server is up; this fires only on persistent 5xx/429.
- If 429, raise the server rate limit or stagger startup asset fetches via maxParallelDownloads.
- If transient, retry dotnet.create() — the loader does its own internal retries so an outer retry covers longer outages.
- If the file genuinely does not exist, you will get 102/104 (404 is non-retryable) so first confirm the status is 5xx/429.
- Disable retry if it masks a real problem: loaderConfig.enableDownloadRetry=false (then a single 5xx surfaces immediately as 102).
Example fix
// before: default retry, persistent 503
builder.withConfig({ /* enableDownloadRetry defaults true */ });
// after: surface the underlying error immediately
builder.withConfig({ enableDownloadRetry: false }); Defensive patterns
Strategy: retry
Validate before calling
// No pre-check; instead detect retryable status from a probe
async function probeForRetry(url: string): Promise<boolean> {
const r = await fetch(url);
return r.status === 429 || r.status >= 500;
} Type guard
function isRetryableStatus(status: number): boolean {
const noRetry = new Set([400,401,403,404,405,406,409,410,411,413,414,415,422,426,501,505]);
return !noRetry.has(status);
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try { return await dotnet.create(); }
catch (e) {
if (/after multiple attempts/.test((e as Error).message) && attempt < 2) {
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
continue;
}
throw e;
}
} Prevention
- Instrument the backend so 5xx during deploys return 503 quickly; the loader retries internally but a long outage needs an outer retry.
- Tune loaderConfig.maxParallelDownloads lower on rate-limited servers to avoid 429 storms.
- Keep enableDownloadRetry=true (default) in production for resilience; turn it off in tests for fast failures.
- Monitor server uptime; this error means persistent failure, not a blip.
When it happens
Trigger: Server returns 500/502/503/429 persistently across the three attempts. Transient outage longer than ~200ms total backoff. Rate-limited endpoint returning 429 even after the 100ms delay. Custom loader returning non-2xx on every attempt.
Common situations: Backend deploy in progress. Mobile flaky connection mid-startup. Aggressive rate limiter (429). Cold CDN origin returning 502. asset.behavior is retryable (assembly/icu/etc) — dotnetwasm never reaches this path because noRetry['dotnetwasm'].
Related errors
- Failed to load resource
- Azure failed to download
- BrowserHttpWriteStream.Rejected
- BrowserHttpWriteStream.Rejected
- Couldn't determine newest 'main' git hash
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/2f0d1e122c8a2e84.
Report an issue: GitHub.
Appendix: source
Thrown at src/native/libs/Common/JavaScript/loader/assets.ts:426
}
// second attempt only after all first attempts are queued
await allDownloadsQueuedPCS.promise;
if (response.status === 429) {
// Too Many Requests
await delay(100);
}
dotnetLogger.debug(`Retrying download '${asset.name}'`);
response = await loadResourceAttempt();
if (response.ok || noRetryStatusCodes.has(response.status)) {
return response;
}
await delay(100); // wait 100ms before the last retry
dotnetLogger.debug(`Retrying download (2) '${asset.name}' after delay`);
response = await loadResourceAttempt();
if (response.ok) {
return response;
}
throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}' after multiple attempts. Last HTTP status: ${response.status} ${response.statusText}`);
async function loadResourceAttempt(): Promise<Response> {
let response: Response;
try {
response = await loadResourceThrottle(asset);
if (!response) {
response = responseLike(asset.resolvedUrl!, null, {
status: 404,
statusText: "No response",
});
}
} catch (err: any) {
response = responseLike(asset.resolvedUrl!, null, {
status: 500,
statusText: err.message || "Exception during fetch",
});
}
return response;View on GitHub (pinned to 60108ba66e)