bytedance/deer-flow · error

Upload failed

Error message

Upload failed

What it means

uploadFiles POSTs multipart form data ('files' entries) to /api/threads/{threadId}/uploads and throws on any non-ok response. readErrorDetail prefers the gateway's error detail (e.g. file too large, disallowed type) and falls back to 'Upload failed' when the body has none.

Source

Thrown at frontend/src/core/uploads/api.ts:70

  threadId: string,
  files: File[],
): Promise<UploadResponse> {
  const formData = new FormData();

  files.forEach((file) => {
    formData.append("files", file);
  });

  const response = await fetch(
    `${getBackendBaseURL()}/api/threads/${threadId}/uploads`,
    {
      method: "POST",
      body: formData,
    },
  );

  if (!response.ok) {
    throw new Error(await readErrorDetail(response, "Upload failed"));
  }

  return response.json();
}

/**
 * Load the upload limits enforced by the gateway for a thread
 */
export async function getUploadLimits(threadId: string): Promise<UploadLimits> {
  const response = await fetch(
    `${getBackendBaseURL()}/api/threads/${threadId}/uploads/limits`,
  );

  if (!response.ok) {
    throw new Error(
      await readErrorDetail(response, "Failed to load upload limits"),
    );
  }

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Fetch and enforce the limits first via getUploadLimits(threadId) — validate size/type client-side before the POST.
  2. Match the error detail the gateway returned (it names the actual limit or reason) in the Network tab.
  3. 401: re-authenticate; 404: verify the thread still exists before retrying.
  4. 5xx: check gateway logs for the uploads handler and storage mount health.

Example fix

// before: fire and hope
const res = await uploadFiles(threadId, files);

// after: pre-validate against gateway limits
const limits = await getUploadLimits(threadId);
const oversized = files.filter((f) => f.size > limits.max_file_size);
if (oversized.length) { throw new Error(`File exceeds ${limits.max_file_size} bytes: ${oversized[0].name}`); }
const res = await uploadFiles(threadId, files);
Defensive patterns

Strategy: validation

Validate before calling

const limits = await getUploadLimits(threadId);
const tooBig = files.filter((f) => f.size > limits.max_file_size);
const tooMany = files.length > limits.max_files;
if (tooBig.length || tooMany) { /* block and explain before POST */ }

Type guard

function isWithinLimits(files: File[], limits: UploadLimits): boolean {
  return files.length <= limits.max_files && files.every((f) => f.size <= limits.max_file_size);
}

Try / catch

try { const res = await uploadFiles(threadId, files); } catch (e) { toast(e.message || 'Upload failed'); keepFilesSelectedForRetry(); }

Prevention

When it happens

Trigger: Uploading a file exceeding the gateway's size limit, a disallowed MIME type/extension, uploading to a deleted/nonexistent thread id (404), expired session (401), or gateway storage backend failure (5xx).

Common situations: Users attaching large videos/archives past the configured limit, uploading immediately after session expiry, thread deleted in another tab while the upload dialog was open.

Related errors


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