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

  1. Check whether the server is up; this fires only on persistent 5xx/429.
  2. If 429, raise the server rate limit or stagger startup asset fetches via maxParallelDownloads.
  3. If transient, retry dotnet.create() — the loader does its own internal retries so an outer retry covers longer outages.
  4. If the file genuinely does not exist, you will get 102/104 (404 is non-retryable) so first confirm the status is 5xx/429.
  5. 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

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


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)