{"record":{"id":"2f0d1e122c8a2e84","repo":"dotnet/runtime","slug":"failed-to-load-resource-asset-name-from-as-2f0d1e","errorCode":null,"errorMessage":"Failed to load resource '${asset.name}' from '${asset.resolvedUrl}' after multiple attempts. Last HTTP status: ${response.status} ${response.statusText}","messagePattern":"Failed to load resource '(.+?)' from '(.+?)' after multiple attempts\\. Last HTTP status: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/native/libs/Common/JavaScript/loader/assets.ts","lineNumber":426,"sourceCode":"    }\n    // second attempt only after all first attempts are queued\n    await allDownloadsQueuedPCS.promise;\n    if (response.status === 429) {\n        // Too Many Requests\n        await delay(100);\n    }\n    dotnetLogger.debug(`Retrying download '${asset.name}'`);\n    response = await loadResourceAttempt();\n    if (response.ok || noRetryStatusCodes.has(response.status)) {\n        return response;\n    }\n    await delay(100); // wait 100ms before the last retry\n    dotnetLogger.debug(`Retrying download (2) '${asset.name}' after delay`);\n    response = await loadResourceAttempt();\n    if (response.ok) {\n        return response;\n    }\n    throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}' after multiple attempts. Last HTTP status: ${response.status} ${response.statusText}`);\n\n    async function loadResourceAttempt(): Promise<Response> {\n        let response: Response;\n        try {\n            response = await loadResourceThrottle(asset);\n            if (!response) {\n                response = responseLike(asset.resolvedUrl!, null, {\n                    status: 404,\n                    statusText: \"No response\",\n                });\n            }\n        } catch (err: any) {\n            response = responseLike(asset.resolvedUrl!, null, {\n                status: 500,\n                statusText: err.message || \"Exception during fetch\",\n            });\n        }\n        return response;","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/native/libs/Common/JavaScript/loader/assets.ts#L408-L444","documentation":"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).","triggerScenarios":"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.","commonSituations":"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'].","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)."],"exampleFix":"// before: default retry, persistent 503\nbuilder.withConfig({ /* enableDownloadRetry defaults true */ });\n\n// after: surface the underlying error immediately\nbuilder.withConfig({ enableDownloadRetry: false });","handlingStrategy":"retry","validationCode":"// No pre-check; instead detect retryable status from a probe\nasync function probeForRetry(url: string): Promise<boolean> {\n  const r = await fetch(url);\n  return r.status === 429 || r.status >= 500;\n}","typeGuard":"function isRetryableStatus(status: number): boolean {\n  const noRetry = new Set([400,401,403,404,405,406,409,410,411,413,414,415,422,426,501,505]);\n  return !noRetry.has(status);\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try { return await dotnet.create(); }\n  catch (e) {\n    if (/after multiple attempts/.test((e as Error).message) && attempt < 2) {\n      await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));\n      continue;\n    }\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["network","retry","http-status","transient"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}