You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the operating-system temporary directory and Gradio's upload directory are on different filesystems or Windows volumes, /gradio_api/upload can return a content-addressed cache path before the uploaded file has been moved to that path.
Note
Two environments are intentionally reported:
Original incident: Python 3.12.13, Gradio 5.50.0, Starlette 0.52.1, FaceFusion, and a 507 MB MP4. This is where the intermittent PermissionError and process exit were observed.
Latest-version isolated reproduction: Python 3.13.5, Gradio 6.20.0, and a generated 32 MiB payload. This deterministically demonstrates that the underlying return-before-publication race still exists in the latest release. Its immediate read usually observes HTTP 403 before the background move starts, rather than reproducing the exact process-level crash.
Candidate fix validation: based on Gradio main commit 01a06729c94916c2859886dca2971b4857c09741.
The main route calls upload_fn(..., force_move=False). If os.rename() fails across filesystems, upload_fn() returns the source and destination lists, the route schedules move_uploaded_files_to_cache() as a Starlette background task, and the response immediately exposes the destination path. A client can request that path while it is missing, partially copied, or still held by the copying process.
In a FaceFusion UI, an immediate browser Range request for a 507 MB MP4 intermittently overlapped that window and reached starlette.responses.FileResponse._handle_single_range -> anyio.open_file(path, "rb") -> PermissionError. The full service process then exited. The exact failure is timing-dependent, but the premature publication itself is deterministic.
Relevant source paths on main commit 01a06729c94916c2859886dca2971b4857c09741:
gradio/route_utils.py, upload_fn() around lines 1399-1463
gradio/routes.py, the main upload route around lines 1786-1813
gradio/static_server.py around lines 98-116; this caller also passes force_move=False but discards the pending move lists
Deterministic 32 MiB probe results on released Gradio 6.20.0:
Observation
Baseline
Candidate fix
Upload response time
0.971 s
0.985 s
Destination exists at response
no
yes
Complete size at response
missing
33,554,432 bytes
Immediate 1 KiB Range GET
HTTP 403
HTTP 206
Expected behavior: once the upload endpoint returns a cache path, that path should name a complete, readable file. File-serving requests should never observe publication in progress.
Proposed fix: create multipart temporary files inside the configured Gradio upload directory, then close and atomically rename each file into its content-addressed subdirectory before returning. This keeps temporary and final files on the same filesystem, removes the background-copy visibility window, and preserves content-addressed cache reuse.
I prepared a candidate patch and focused tests. Seven upload/static-worker tests pass, along with formatting, syntax, and focused lint checks. A local bundle containing the standalone reproduction, raw baseline/fixed JSON evidence, validation record, and patch is ready and can be provided if useful.
Have you searched existing issues? 🔎
I have searched and found no existing issues
Reproduction
Prerequisite: on Windows, keep TEMP/TMP on C: and choose a clean Gradio upload directory on D:. Install gradio==6.20.0 and httpx.
Run python app.py, then python probe.py in another terminal. On the affected release, the first line reports that the returned destination is missing, the immediate Range GET is HTTP 403, and the complete file appears after the background task finishes. The delay does not introduce the ordering bug; it only makes the existing return-before-publication interval deterministic.
Screenshot
Not applicable. This is a server-side upload publication race; the deterministic probe output and raw observations are described above. A local evidence bundle is available on request.
Logs
Original incident log context (FaceFusion, Python 3.12.13, Gradio 5.50.0, Starlette 0.52.1) while the browser requested the uploaded 507 MB MP4 preview:
ERROR: Exception in ASGI application
uvicorn h11_impl.run_asgi
-> starlette middleware
-> starlette.responses.FileResponse._handle_single_range
-> anyio.open_file(path, "rb")
-> PermissionError: [Errno 13] Permission denied:
'D:\\...\\gradio\\<hash>\\video.mp4'
The deterministic minimal probe usually observes the earlier part of the same window and receives HTTP 403 because the destination does not yet exist. Once the cross-volume copy starts creating the destination, a concurrent Range read can instead overlap an incomplete or locked file.
System Info
Original intermittent incident
Operating System: Windows 11 Home 64-bit
Application: FaceFusion
Payload: 507 MB MP4
Python: 3.12.13
gradio: 5.50.0
starlette: 0.52.1
Describe the bug
When the operating-system temporary directory and Gradio's upload directory are on different filesystems or Windows volumes,
/gradio_api/uploadcan return a content-addressed cache path before the uploaded file has been moved to that path.Note
Two environments are intentionally reported:
PermissionErrorand process exit were observed.01a06729c94916c2859886dca2971b4857c09741.The main route calls
upload_fn(..., force_move=False). Ifos.rename()fails across filesystems,upload_fn()returns the source and destination lists, the route schedulesmove_uploaded_files_to_cache()as a Starlette background task, and the response immediately exposes the destination path. A client can request that path while it is missing, partially copied, or still held by the copying process.In a FaceFusion UI, an immediate browser Range request for a 507 MB MP4 intermittently overlapped that window and reached
starlette.responses.FileResponse._handle_single_range -> anyio.open_file(path, "rb") -> PermissionError. The full service process then exited. The exact failure is timing-dependent, but the premature publication itself is deterministic.Relevant source paths on main commit
01a06729c94916c2859886dca2971b4857c09741:gradio/route_utils.py,upload_fn()around lines 1399-1463gradio/routes.py, the main upload route around lines 1786-1813gradio/static_server.pyaround lines 98-116; this caller also passesforce_move=Falsebut discards the pending move listsDeterministic 32 MiB probe results on released Gradio 6.20.0:
Expected behavior: once the upload endpoint returns a cache path, that path should name a complete, readable file. File-serving requests should never observe publication in progress.
Proposed fix: create multipart temporary files inside the configured Gradio upload directory, then close and atomically rename each file into its content-addressed subdirectory before returning. This keeps temporary and final files on the same filesystem, removes the background-copy visibility window, and preserves content-addressed cache reuse.
I prepared a candidate patch and focused tests. Seven upload/static-worker tests pass, along with formatting, syntax, and focused lint checks. A local bundle containing the standalone reproduction, raw baseline/fixed JSON evidence, validation record, and patch is ready and can be provided if useful.
Have you searched existing issues? 🔎
Reproduction
Prerequisite: on Windows, keep
TEMP/TMPonC:and choose a clean Gradio upload directory onD:. Installgradio==6.20.0andhttpx.app.py:probe.py:Run
python app.py, thenpython probe.pyin another terminal. On the affected release, the first line reports that the returned destination is missing, the immediate Range GET is HTTP 403, and the complete file appears after the background task finishes. The delay does not introduce the ordering bug; it only makes the existing return-before-publication interval deterministic.Screenshot
Not applicable. This is a server-side upload publication race; the deterministic probe output and raw observations are described above. A local evidence bundle is available on request.
Logs
System Info
Original intermittent incident
Latest-version isolated reproduction
Candidate fix source base
Severity
I can work around it