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
- Fetch and enforce the limits first via getUploadLimits(threadId) — validate size/type client-side before the POST.
- Match the error detail the gateway returned (it names the actual limit or reason) in the Network tab.
- 401: re-authenticate; 404: verify the thread still exists before retrying.
- 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
- Always fetch limits once per thread and validate client-side first.
- Show per-file size/type feedback at selection time.
- Preserve the user's selection on failure so a retry is one click.
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
- Failed to load upload limits
- Failed to prepare ${failedConversions} attachment(s) for upl
- Thread is not ready for file upload.
- Failed to list uploaded files
- Failed to delete file
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/93dd3ed8c7bfa167.
Report an issue: GitHub.