快速结论:当系统临时目录与 Gradio 上传/缓存目录位于不同文件系统或不同 Windows 卷(例如 TEMP 在 C:、上传目录在 D:)时,/gradio_api/upload 会先返回 content-addressed 缓存路径,再在后台执行跨文件系统拷贝,导致客户端在该路径尚未就绪时读取就触发错误。优先排查临时目录与上传目录是否跨卷,以及是否使用了最新版本。
适用环境:Issue 中确认的环境包括 Gradio 5.50.0 / 6.20.0、Python 3.12.13 / 3.13.5、Starlette 0.52.1 / 0.52.1+、FaceFusion 以及 Windows 跨卷场景(TEMP/TMP 在 C:,上传目录在 D:);候选修复基于 Gradio main 提交 01a06729c94916c2859886dca2971b4857c09741。其他 Python、CUDA、显卡信息 Issue 未提供。
最快修复方案:暂无确认的一步修复方案。Issue 的修复补丁尚未合并进已发布版本,官方 release 中无法直接升级消除;可优先尝试将 Gradio 上传目录与系统临时目录放在同一文件系统/卷,以规避跨文件系统回退路径。若使用主分支或候选补丁,可参考 SAUMustapure:fix/upload-atomic 对比。
注意事项:将临时目录与上传目录置于同一卷只是规避手段,并非 Issue 中已验证的代码级修复;该改动的正式补丁尚未合入。采用候选补丁属于未发布变更,跨文件系统上传会同步拷贝,会增加延迟(Issue 中对比显示上传响应时间从 0.971 s 增至 0.985 s),但可避免部分拷贝或文件被占用时的读取失败。
问题场景
用户在 Windows 上运行 Gradio 应用(Issue 中为 FaceFusion UI),将系统临时目录(TEMP/TMP)放在 C:,把 Gradio 上传目录放在 D:。当通过 /gradio_api/upload 上传较大文件(例如 507 MB MP4)后,客户端立即对该缓存路径发起 Range 请求(浏览器播放/预览场景常见),可能读取到尚未完成跨文件系统移动的文件,导致 FileResponse 打开文件失败。
报错原文
Upload route returns cache path before cross-filesystem move completes
starlette.responses.FileResponse._handle_single_range -> anyio.open_file(path, "rb") -> PermissionError
Immediate 1 KiB Range GET: HTTP 403 (baseline) / HTTP 206 (candidate fix)
原因分析
最可能的原因是上传路由的发布时序问题:主上传路由调用 upload_fn(..., force_move=False)。当 os.rename() 跨文件系统失败时,upload_fn() 返回源/目标列表,路由把 move_uploaded_files_to_cache() 注册为 Starlette 后台任务,但响应已立即暴露目标缓存路径。客户端可能在目标文件缺失、只被部分拷贝、或仍被拷贝进程占用时请求该路径。于是出现两种表现:早期读通常得到 HTTP 403,时序更巧时(如大文件 Range 请求)会命中 anyio.open_file 的 PermissionError,在 FaceFusion 场景中甚至导致整个服务进程退出。Issue 指出该提前发布是确定性的,而具体崩溃报告是时序相关的。
环境排查
- 确认 Gradio 版本:Issue 在 5.50.0 观察到崩溃,在 6.20.0 仍可确定性地复现返回早于发布的问题。
- 确认 Python 版本:Issue 涉及 3.12.13 与 3.13.5。
- 确认 Starlette 版本:Issue 涉及 0.52.1。
- 确认 Windows 盘符布局:
TEMP/TMP是否与 Gradio 上传目录(GRADIO_TEMP_DIR)位于不同卷,这是触发跨文件系统回退的关键。 - 确认上传文件大小与客户端是否在响应后立即发起 Range 请求。
- 确认是否使用了
force_move=False的调用路径,以及静态服务器路径gradio/static_server.py是否同样丢弃了待处理移动列表。
解决步骤
- 先确认问题是否由跨卷引发:在 Windows 上检查
TEMP、TMP与GRADIO_TEMP_DIR(或默认上传目录)是否属于同一盘符/文件系统。 - 可优先尝试规避:将 Gradio 上传目录与系统临时目录放到同一文件系统/卷,或者在应用启动前把
TEMP/TMP指向上传目录所在卷,使os.rename()走同文件系统重命名路径,从而不再进入跨文件系统后台拷贝分支。 - 若希望从代码层修复,可试用 Issue 中候选补丁思路:在跨文件系统回退时,把上传文件先拷贝到上传/缓存目录内的临时文件,执行 fsync 并关闭,再用
os.replace()原子发布,最后才返回目标路径。 - 若要评估候选补丁,可参考对比链接创建分支并运行 Issue 提到的测试:
test/test_publish_atomic.py(原子发布 helper 的单元测试)与test/test_upload_integration.py(monkeypatchos.rename模拟跨文件系统失败,断言返回路径立即可读)。 - 等待官方合入:Issue 已关闭,但补丁是否进入某正式版本请以官方 release 说明为准,本文不推断版本号。
验证方法
复现侧:在跨卷环境中上传文件后,立即对返回的缓存路径发起 1 KiB Range GET;修复前通常得到 HTTP 403(或偶发 PermissionError),修复后应返回 HTTP 206,且目标文件在响应时已存在且大小完整(Issue 中 32 MiB 探测为 33,554,432 bytes)。集成侧:运行 test/test_upload_integration.py,确认返回路径在返回时即可读;运行 test/test_publish_atomic.py 确认原子发布 helper 行为正确。Issue 报告候选补丁下七个上传/静态 worker 测试通过。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


![[BUG] pre-commit hooks fail on Windows due to hardcoded Unix virtual environment path](https://www.chat-gpts.plus/wp-content/uploads/2026/09/6863-cec8e132-768x403.jpg)