bytedance/deer-flow · warning · HTTPException
File not found: {filename}
Error message
File not found: {filename} What it means
HTTP 404 raised by DELETE /threads/{thread_id}/uploads/{filename} when the storage layer raises FileNotFoundError — the named file does not exist in that thread's uploads directory. Ownership/permission checks (owner_check=True, require_existing=True) have already passed, so this is purely a missing-file signal.
Source
Thrown at backend/app/gateway/routers/uploads.py:476
@require_permission("threads", "read", owner_check=True)
async def list_uploaded_files(thread_id: ThreadId, request: Request) -> UploadListResponse:
"""List all files in a thread's uploads directory."""
try:
result = await run_file_io(_list_uploaded_files_for_thread, thread_id, get_effective_user_id())
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return UploadListResponse(**result)
@router.delete("/{filename}")
@require_permission("threads", "delete", owner_check=True, require_existing=True)
async def delete_uploaded_file(thread_id: ThreadId, filename: str, request: Request) -> dict:
"""Delete a file from a thread's uploads directory."""
try:
return await run_file_io(_delete_uploaded_file_for_thread, thread_id, filename, get_effective_user_id())
except FileNotFoundError:
raise HTTPException(status_code=404, detail=f"File not found: {filename}")
except PathTraversalError:
raise HTTPException(status_code=400, detail="Invalid path")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Failed to delete {filename}: {e}")
raise HTTPException(status_code=500, detail=f"Failed to delete {filename}: {str(e)}")
View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Refresh the file list (GET .../uploads/list) and delete using the exact filename it returns.
- Treat 404 on delete as success if the goal is 'file gone' (idempotent delete in the client).
Example fix
// before
await api.deleteUpload(tid, name); // throws on 404
// after
try { await api.deleteUpload(tid, name); }
catch (e) { if (e.status !== 404) throw e; /* already gone */ } Defensive patterns
Strategy: try-catch
Validate before calling
const files = await fetch(`/api/threads/${tid}/uploads/list`).then(r => r.json());
const target = files.files.find(f => f.filename === name);
if (!target) return; // nothing to delete Try / catch
catch (e) { if (e.status === 404) return; /* treat as deleted */ throw e; } Prevention
- Make client deletes idempotent by swallowing 404.
- Always delete by the filename returned from the list endpoint, not the originally uploaded name.
When it happens
Trigger: DELETE on an uploads filename that was never uploaded, was already deleted, or whose on-disk name differs from the URL segment (e.g. it was renamed by duplicate-handling suffixing or normalized).
Common situations: UI list is stale after a concurrent delete; filename was normalized/suffixed at upload time so the client's original name no longer matches; retrying a delete that already succeeded.
Related errors
- Failed to delete file
- Failed to delete local thread data.
- No feedback found for this run
- Feedback {feedback_id} not found
- Memory fact '{fact_id}' not found.
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/fcb629a3b5efcf43.
Report an issue: GitHub.