{"record":{"id":"fcb629a3b5efcf43","repo":"bytedance/deer-flow","slug":"file-not-found-filename","errorCode":null,"errorMessage":"File not found: {filename}","messagePattern":"File not found: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"backend/app/gateway/routers/uploads.py","lineNumber":476,"sourceCode":"@require_permission(\"threads\", \"read\", owner_check=True)\nasync def list_uploaded_files(thread_id: ThreadId, request: Request) -> UploadListResponse:\n    \"\"\"List all files in a thread's uploads directory.\"\"\"\n    try:\n        result = await run_file_io(_list_uploaded_files_for_thread, thread_id, get_effective_user_id())\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n    return UploadListResponse(**result)\n\n\n@router.delete(\"/{filename}\")\n@require_permission(\"threads\", \"delete\", owner_check=True, require_existing=True)\nasync def delete_uploaded_file(thread_id: ThreadId, filename: str, request: Request) -> dict:\n    \"\"\"Delete a file from a thread's uploads directory.\"\"\"\n    try:\n        return await run_file_io(_delete_uploaded_file_for_thread, thread_id, filename, get_effective_user_id())\n    except FileNotFoundError:\n        raise HTTPException(status_code=404, detail=f\"File not found: {filename}\")\n    except PathTraversalError:\n        raise HTTPException(status_code=400, detail=\"Invalid path\")\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n    except Exception as e:\n        logger.error(f\"Failed to delete {filename}: {e}\")\n        raise HTTPException(status_code=500, detail=f\"Failed to delete {filename}: {str(e)}\")\n","sourceCodeStart":458,"sourceCodeEnd":484,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/uploads.py#L458-L484","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)."],"exampleFix":"// before\nawait api.deleteUpload(tid, name); // throws on 404\n\n// after\ntry { await api.deleteUpload(tid, name); }\ncatch (e) { if (e.status !== 404) throw e; /* already gone */ }","handlingStrategy":"try-catch","validationCode":"const files = await fetch(`/api/threads/${tid}/uploads/list`).then(r => r.json());\nconst target = files.files.find(f => f.filename === name);\nif (!target) return; // nothing to delete","typeGuard":null,"tryCatchPattern":"catch (e) { if (e.status === 404) return; /* treat as deleted */ throw e; }","preventionTips":["Make client deletes idempotent by swallowing 404.","Always delete by the filename returned from the list endpoint, not the originally uploaded name."],"tags":["upload","http-404","delete","idempotency"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}