缺陷报告:SenseVoice 本地模型下载失败 —— Host 未跟随 HTTP 重定向(308 / 302) #7822
Replies: 1 comment
按设计应当跟随重定向——所以一个 308 漏到用户面前,本身就是缺陷1. 该模块的 README 把重定向这件事写明了
⇒ 也就是说:"跟随重定向"是有意设计的一部分,而且是经 Host 的 fetch 代理完成的。你看到的"下载服务返回 HTTP 308"意味着这条代理路径没有把重定向跟下去——不是 2. 你的排除工作正好把这一定位撑住了你列的"同机同网下 curl、Node v24.9.0、Node v24.20.0、undici 8.10/8.11 全部能跟"是最有说服力的一段——它排除了"网络/代理/目标站点"这一整层,把问题锁在Harness 侧那条出站 HTTP 路径上。建议把这段放在报告最前面。 3. 一个值得你去交叉验证的线索(假设,不是结论)同期另有一条独立报告(#7468)说 ⇒ 如果你能在报告里加一句"这条下载是否也经同一条代理路径"(以及它和 #7468 是否同因),维护者很可能一次修掉两处。我这边无法确认两者同因,所以请把它写成待验证的关联,而不是定论。 4. 建议补的证据(能让它几分钟内被确认)
5. 版本提醒你写的是桌面版
6. 一句总结这不是"HF-Mirror 抽风",而是设计里承诺了跟随重定向、实际没跟——按缺陷报是对的,而且你已经有把它钉死的全部素材。 |
Uh oh!
There was an error while loading. Please reload this page.
摘要
在 DeepSeek Harness 桌面版中启用「语音输入」Bundle 并准备 SenseVoice 本地模型时,下载固定失败:
hf-mirror.com与huggingface.co都会把resolve/<revision>/<file>重定向到实际 CDN。报错说明 Host 进程收到的就是一个 308 响应,重定向没有被跟随。同一台机器、同一网络下,curl、Node v24.9.0、Node v24.20.0、undici 8.10/8.11 均能正常跟随并下载成功,因此问题定位在 Harness 侧的出站 HTTP 行为。影响:所有走 HTTP 重定向的模型下载资源都无法通过 Harness 自身的下载通道获取(至少包括 SenseVoice INT8 与 Silero VAD)。用户只能手动放置文件绕过。
环境
0.1.7-rc.2(玲珑包com.deepseek.dsh-desktop)<prefix>/node/bin/node<prefix>/harness/lib/bin.jsweb --patch <overlay> --port 34563(见apps/desktop-launcher/internal/appenv/env.go:55-68)harness/node_modules/undici)mixed-port: 7897、allow-lan: false、TUN 开启(gvisor、auto-route)127.0.0.1:7897直达,DNS 走 fake-ip(198.18.0.0/15 路由至 TUN)复现步骤
dsh-experimental-speech-to-text、-speech-to-text-sensevoice、-api-speech-to-text、-client-ui-voice-input)。期望:模型下载并校验通过,状态变为「本地语音已就绪」。
实际:
准备失败 —— 无法下载 model.int8.onnx:下载服务返回 HTTP 308。下载来源:https://hf-mirror.com证据
1. 服务端行为完全正常
资源固定 URL(来自
packages/experimental/speech-to-text-sensevoice/runtime/assets.json):两个源的重定向链(
curl -D -,不跟随 vscurl -L跟随):huggingface.co302→us.aws.cdn.hf.co200/206hf-mirror.com308→huggingface.co同路径302→us.aws.cdn.hf.co200/206curl -L:num_redirects=2,最终206(带 Range)。请求头变体测试(默认 /
accept-encoding: gzip, deflate, br/ Chrome UA /Range: bytes=0-/Accept: */*/ 去掉 UA)全部稳定返回308且带Location,排除了 UA 或请求头导致响应差异的可能。2. UI 的「下载来源」字段证明重定向未发生
packages/experimental/speech-to-text-sensevoice/src/runtime.ts:114-119:source取自response.url。UI 显示下载来源:https://hf-mirror.com,而报错reason: 'http'、status: 308—— 二者结合说明:fetch返回了一个响应(不是抛网络异常);status === 308,且response.url仍停留在hf-mirror.com,即重定向没有发生。3. 两个源都失败,UI 显示的是最后一个错误
runtime.ts:161-174按源逐个重试,并且抛出最后一个错误:代理侧日志(mihomo
/logs实时流)在点击「重试准备」窗口内捕获到:两个域名都被成功请求——证明网络链路完全通畅;也印证了 HF(302)与 hf-mirror(308)两个源都失败,UI 呈现的是顺序上最后一个(
hf-mirror)的错误。4. 命令行无法复现(关键对照)
同一台机器、同一容器、同一 Clash、同一 URL:
curl -L200/206,redirected✅fetchstatus=200、redirected=true、url=us.aws.cdn.hf.co✅fetchfetchinstallProxyFromEnvironment()装好全局 dispatcher,再fetchfetch(url, { redirect: 'manual' })对照status=308、redirected=false、url=hf-mirror.com← 与产品表现逐字吻合即:产品表现等价于在 Host 进程内以
redirect: 'manual'发起了请求。分析
服务端链路正常,客户端链路正常,差异只存在于 Harness 完整进程内。而语音包本身使用裸全局
fetch,未引入任何 fetch 封装或redirect覆盖:runtime.ts:114→await fetch(asset.url, { signal })dsh-experimental-speech-to-text-sensevoice/lib/index.js:266)与源码一致dependencies不含dsh-web-fetch-http等封装包,无import ... as fetch绑定globalThis.fetch =:无命中因此需要排查在完整 Harness 启动后,
globalThis.fetch的行为为何退化为不跟随重定向。待验证线索(均已定位到代码,但尚未证实因果)
线索 A ——
setGlobalDispatcher作用的 undici 与globalThis.fetch使用的 undici 可能不是同一份。packages/util/http-proxy/src/install.ts通过await import('undici')动态导入:globalThis.fetch使用 Node 内置 undici 7.16.0;import('undici')解析到 自带node_modules/undici8.11.0。两者是不同的模块实例,其「全局 dispatcher」状态很可能并不共享。若成立,则
installProxyFromEnvironment()的安装对globalThis.fetch无效(这本身也是一个独立缺陷,与本次重定向问题可能相关也可能无关)。建议先确认该假设是否成立。线索 B —— 是否存在插件在 Host 侧包装
globalThis.fetch。仓库内已知存在一处替换:
packages/experimental/webworker-runtime/src/polyfill/async-context/async-context-hooks.ts:69:该实现透传
init,理论上不影响redirect;但需要确认它在 Host 进程中的实际加载范围,以及是否存在其它未纳入搜索的包装点(如打包拼装、动态 import)。线索 C —— 出站请求策略是否被统一收口。
packages/web/web-fetch-http/lib/index.js:163,197使用redirect: "manual"(这是为web_fetch工具做 SSRF 防护的主动设计)。需要确认该策略是否通过某个公共出站入口被扩展到其它调用方。建议的最小复现
在 Harness Host 进程内(而非外部 shell)执行:
200 true https://us.aws.cdn.hf.co/...308 false https://hf-mirror.com/...→ 复现成功同时建议打印
globalThis.fetch === (await import('undici')).fetch与(await import('undici')).getGlobalDispatcher() === /* Node 内置 dispatcher */的对比结果,用于验证线索 A。影响范围
model.int8.onnx(239 MB,308)、tokens.txt、silero_vad.onnx(1.8 MB)。model.onnx(FP32,937 MB)等其它走 Hugging Faceresolve路径的资源。临时绕过(已验证可行)
模型就绪判定只校验文件大小 + sha256(
runtime.ts:40-53),手动放置文件即可跳过下载:~/.dsh/speech-to-text/sensevoice/models/sensevoice-onnx/model.int8.onnxc71f0ce00bec95b07744e116345e33d8cbbe08cef896382cf907bf4b51a2cd51~/.dsh/speech-to-text/sensevoice/models/sensevoice-onnx/tokens.txtf449eb28dc567533d7fa59be34e2abca8784f771850c78a47fb731a31429a1dc~/.dsh/speech-to-text/sensevoice/models/silero/silero_vad.onnxa35ebf52fd3ce5f1469b2a36158dba761bc47b973ea3382b3186ca15b1f5af28用
curl -L(能正确跟随重定向)下载即可;放置后点「重试准备」会直接校验通过并显示「本地语音已就绪」。附:本次未改动的说明
排查过程未修改任何 Harness 源码、配置或代理设置;仅新增了上述三个模型缓存文件(约 229 MB)。临时脚本与后台任务均已清理。
All reactions