bytedance/deer-flow · error

Failed to load artifact: ${response.status}

Error message

Failed to load artifact: ${response.status}

What it means

Thrown when loading artifact content via range-GET fails and the status is not the special empty-file case (416 with total 0). The loader uses Range requests for previews, so 416 with a non-zero total (range past EOF after truncation) and 5xx from the Gateway both land here. The error only embeds the numeric status.

Source

Thrown at frontend/src/core/artifacts/loader.ts:63

  const response = await fetch(url, {
    cache: "no-store",
    headers: full
      ? undefined
      : { Range: `bytes=0-${ARTIFACT_PREVIEW_MAX_BYTES - 1}` },
  });
  const contentRange = parseContentRange(response.headers.get("Content-Range"));
  if (response.status === 416 && contentRange?.total === 0) {
    return {
      content: "",
      url,
      truncated: false,
      previewBytes: 0,
      totalBytes: 0,
      sha256: await sha256OfText(""),
    };
  }
  if (!response.ok) {
    throw new Error(`Failed to load artifact: ${response.status}`);
  }

  const bytes = await response.arrayBuffer();
  const truncated =
    !full &&
    response.status === 206 &&
    (contentRange?.end === undefined ||
      contentRange.total > contentRange.end + 1);
  // Streaming decode intentionally holds an incomplete trailing UTF-8 code
  // point instead of fabricating U+FFFD at the range boundary.
  const content = new TextDecoder().decode(bytes, { stream: truncated });
  const etag = response.headers.get("etag");
  const sha256 =
    etag?.match(/^"([0-9a-f]{64})"$/)?.[1] ??
    (!truncated ? await sha256OfText(content) : undefined);
  const contentLengthHeader = response.headers.get("Content-Length");
  const contentLength =
    contentLengthHeader === null ? undefined : Number(contentLengthHeader);

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Retry with full=true (no Range header) once — bypasses range-past-EOF 416s
  2. On 404, re-check the artifact list for the thread and drop dead entries from the UI
  3. Verify nginx forwards Range headers (proxy_set_header Range / proxy_pass behavior) if 200-for-206 symptoms appear
  4. Re-request from offset 0 with the current total to resync after truncation

Example fix

// before
const page = await loadArtifact(url, {full: false});

// after
try {
  const page = await loadArtifact(url, {full: false});
} catch (e) {
  if (e instanceof Error && e.message.includes(': 416')) {
    return loadArtifact(url, {full: true}); // resync after size change
  }
  throw e;
}
Defensive patterns

Strategy: fallback

Type guard

export function isArtifactLoadError(e: unknown, status?: number): boolean {
  return e instanceof Error && /^Failed to load artifact: \d+$/.test(e.message);
}

Try / catch

try {
  return await loadArtifact(url, {full: false});
} catch (e) {
  if (isArtifactLoadError(e) && e.message.endsWith(': 416')) {
    return loadArtifact(url, {full: true}); // range desync: refetch whole
  }
  if (isArtifactLoadError(e) && e.message.endsWith(': 404')) {
    return null; // artifact gone
  }
  throw e;
}

Prevention

When it happens

Trigger: Requesting a range beyond the artifact's current size (file shrank between HEAD and GET); artifact deleted mid-preview (404); Gateway/nginx misconfig dropping Range support so a 200-with-Range mismatch confuses downstream parsing; proxy 502 during Gateway restart.

Common situations: Previewing an artifact that an agent turn is actively rewriting; nginx serving stale cached metadata; opening an old link to a pruned thread's artifact.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/c5610d9e619dcf66. Report an issue: GitHub.