dotnet/runtime · error · Error

Failed to load resource '${asset.name}' from '${asset.resolv

Error message

Failed to load resource '${asset.name}' from '${asset.resolvedUrl}' after multiple attempts. Last HTTP status: ${response.status} ${response.statusText}

What it means

loadResourceRetry tries an asset up to three times (initial + 2 retries, with a 100ms backoff and a 429-specific delay). If the final response is still not ok and its status is not in noRetryStatusCodes (400,401,403,404,405,406,409,410,411,413,414,415,422,426,501,505), it throws this final-after-retries error.

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 290d5ab72c)

Solutions

  1. Inspect the Last HTTP status in the message — 502/503/504 indicates upstream server/CDN problems; 429 indicates rate limiting.
  2. For 429, raise server-side rate limits or stagger asset requests (lower maxParallelDownloads in loaderConfig).
  3. For 502/503/504, verify backend health and increase uptime/restart capacity; retry loading the page once the backend recovers.
  4. If you control the server, ensure all framework assets return 200 reliably and consider a CDN with longer cache TTLs.

Example fix

// before
builder.withConfig({ maxParallelDownloads: 64 }); // overwhelms server → 429s

// after
builder.withConfig({ maxParallelDownloads: 4 }); // gentler ramp, avoids 429 retries
Defensive patterns

Strategy: retry

Validate before calling

// Probe server stability before starting
const codes = await Promise.all([1,2,3].map(() => fetch(base).then(r => r.status).catch(() => 0)));
if (codes.some(c => c >= 500)) console.warn('Backend unstable; expect retry exhaustion');

Try / catch

try { await loadAll(); }
catch (err) {
  if (/after multiple attempts/.test(err.message)) {
    // exponential backoff then reload the page / re-run create()
    await delay(1000); location.reload();
  } else throw err;
}

Prevention

When it happens

Trigger: A retriable HTTP status (500, 502, 503, 504, 429, or transient 200-less failures) persisted across all three attempts. Permanent statuses (4xx listed above) return immediately without this message.

Common situations: Server/CDN instability; rate limiting (429) that the 100ms delay did not absorb; backend overload during a cold start; flaky reverse proxy returning 502/504; deployment in progress causing transient 500s.

Related errors


AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06). Data as JSON: /api/errors/2f0d1e122c8a2e84. Report an issue: GitHub.