Repository navigation
[BUG] DSH Desktop 0.2.2-rc.3 启动崩溃分析报告 #13333
phonknowunliv
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
症状:Windows 客户端启动后弹出「DeepSeek Harness 无法使用」,无法进入界面。
崩溃记录:
crash-*-main.log,source: main、phase: running、[TypeError: fetch failed] { [cause]: Error: read ECONNRESET }。结论:这不是 WAF / 网络问题,也不是 rc.3 引入的网络回归,而是 Desktop 主进程里一条
fire-and-forget 埋点请求的 1 秒 AbortSignal 掐断了 undici 共享连接池,紧接着的欢迎页初始化 RPC
复用了这条半死连接,得到
read ECONNRESET,进而被判为致命错误。已在本机用真实 0.2.2-rc.3 安装包 100% 稳定复现,并验证了规避方案。
0. TL;DR(给维护者)
apps/desktop/src/main.ts:557的void track('desktop_app_launch', {})→
welcome-backend.ts:140用AbortSignal.timeout(1000)发POST /api/productAnalytics/report。collectEnvironment()+getDeviceIdentity()+OTLP 上报),必然超过 1 秒 → 客户端 abort。
127.0.0.1:<port>的 keep-alive 连接;紧接着main.ts:1457 → readWelcomeState()的
POST /api/settings/describe(同一 origin、同一 global dispatcher、无 deadline)复用它,9.0 秒后抛
read ECONNRESET。readWelcomeState()是reconcileBackend()里被 await 的启动步骤(main.ts:680-695),异常进入main.ts:691 reportFatal(error, 'main');此时 backend 已 ready,所以phase: running(main.ts:151)。apps/desktop/src/**、apps/desktop-host/src/**、packages/util/http-proxy/src/**、packages/client/product-analytics/src/**)上字节级一致。这是竞态:只有"首次埋点上报 > 1 秒"才命中。
product-analytics.config.enabled = false)规避并通过真实运行时验证:
report11,083ms → 14ms,settings/describeECONNRESET → 200。1. 环境
@deepseek-ai/dsh-desktop0.2.2-rc.3(安装目录D:\deepseek,扁平安装,2026-11-13 15:30)dshBuildCommit = 6b729fedde0c8fd03af7afdf5a711aa64fe87c3b,dshBuildDirty = false,dshDesktopUpdateChannel = nightlyC:\Users\<user>\.dsh(profile 位于.dsh\profiles\desktop)crash-*-main.log,没有任何crash-*-host.log2. 复现(可在任意装有 rc.3 的机器上执行)
复现脚本见附录 A。做法与
apps/desktop/src/host-process.ts完全一致:ELECTRON_RUN_AS_NODE=1+--expose-internals启动<app.asar>/dsh/node_modules/@deepseek-ai/dsh-desktop-host/lib/index.js <runtimeDir> <profileDir> <primaryRuntime>,保留 IPC 通道;
ready后,先GET <url>(拿 303 +set-cookie,等价authenticateWebHost);track()那两条带AbortSignal.timeout(1000):productAnalytics/enabled→productAnalytics/report→settings/describe→llm/listConfigurableProviders→credentials/describe→account/getState。2.1 实测结果
A. 只发欢迎页 RPC(跳过埋点)
settings/describellm/listConfigurableProviderscredentials/describeaccount/getState→ Host 完全健康,
stderr为空,最后exit 0。B. 重放 shell 精确序列(含 1 秒 deadline)
authenticateWebHostproductAnalytics/enabled(1s deadline)trueproductAnalytics/report(1s deadline)settings/describe(无 deadline)TypeError: fetch failed/read ECONNRESET,9,031ms→ 与用户的两份
crash-*-main.log完全一致;Hoststderr仍为空、exit 0。C. 不给 deadline,测首次
report的真实耗时report#1report#2settings/describe200→ 决定是否命中崩溃的唯一变量,是首次
report是否超过 1 秒。D. 对照实验
DSH_TELEMETRY_DISABLED=1settings/describe仍 ECONNRESET(该变量不控制 product analytics)product-analytics.config.enabled: false3. 根因
3.1 崩溃链路
3.2 为什么是
phase: running而不是startupbackendReady只在state.phase === 'ready'时置真(main.ts:611-613);ready在host.start()(含authenticateWebHost)完成后才发布(backend-controller.ts:80-84);readWelcomeState()在reconcileBackend()里位于backend.start()之后(
main.ts:682-686)。⇒ 失败点必定在 Host 就绪之后,
phase只能是running。这也是"Host 没问题"的旁证。3.3 为什么与 WAF 无关
ENOTFOUND/EAI_AGAIN/ENETUNREACH,不可能是ECONNRESET;报错指向
127.0.0.1:<port>的 loopback 连接。GET https://harness.deepseek.com/api/v0/check_client_update?scenario=launch(
apps/desktop/src/mandatory-update-policy.ts:161-167,193-196,main.ts:1589触发,不带任何
User-Agent,只有x-client-*)。它的异常在check()内部被完全捕获(
mandatory-update-policy.ts:207),只会让"强制更新策略"不可用,不会弹崩溃框。dsh-otel-collector.deepseeksvc.com/harness.deepseek.com这类链路变慢或失败,从而把首次
report推到 1 秒以上,提高了命中概率。3.4 为什么 rc.2 可能不崩
竞态,不是逻辑差异。两版在这条链路上字节级一致(见第 4 节)。是否命中取决于
"首次
report是否 > 1 秒",会随机器负载、网络、首次运行的缓存状态变化。4. 版本对比结论(排除"rc.3 网络回归")
对两个源码目录做全量
git diff --no-index+ 全树 SHA-256 双向核对:package.json只改版本号0.2.2-rc.2 → 0.2.2-rc.3;pnpm-lock.yaml变更 纯新增、0 删除 ⇒ 没有任何依赖解析变动(electron 44.7.0 / electron-updater 6.8.9 / ws 8.21.3 / zod 4.6.6 / undici 均未动);
packages/experimental/deepseek-chat-import/(默认不加载)、official-bundles名单 +3 行、客户端 UI 插槽重构(ui-sidebar/ui-workspace/ui-primitives/ui-directory-picker-*)、session-log-export导出健壮性;apps/desktop/src/**、apps/desktop-host/src/**、packages/boot/app-boot/src/**、packages/util/http-proxy/src/**、packages/client/product-analytics/src/**、packages/credentials/deepseek-account/src/**、packages/llm/llm/src/**、packages/telemetry/otel/src/**。同时从安装包
app.asar里确认:bundled@deepseek-ai/dsh的依赖中包含@deepseek-ai/dsh-deepseek-chat-import@0.2.2-rc.3—— 即用户安装的 rc.3 与给出的 rc.3 源码一致,不是"同号不同构建"。
5. 修复建议
P0(一行级,直接消除崩溃)
apps/desktop/src/welcome-backend.ts:136-140:不要用 1 秒 deadline 在共享连接池上发埋点请求。两种都可行:
@deepseek-ai/dsh-http-proxy的proxyRouteFor(url)/createRouteDispatcher,见scripts/verify-no-bare-dispatcher.ts),让它的 abort 不可能污染欢迎页 RPC 的池;或
AbortSignal.timeout(1000)放宽/去掉,并把该请求改为启动后再发(
main.ts:557的track('desktop_app_launch')延后到openInitialWindow()之后)。P1(提高启动韧性)
apps/desktop/src/main.ts:1457 / welcome-backend.ts:58:readWelcomeState()对TypeError: fetch failed(transport 类)做一次有界重试。单条被污染的连接不应该能杀死整个启动。P2(Host 侧,消除 11 秒阻塞)
packages/client/product-analytics/src/index.ts:99-129:report()不应在 RPC 调用栈里同步等待collectEnvironment()+getDeviceIdentity()。建议"立即 ack、后台补齐环境字段"(事件先入队,环境属性就绪后 enrich/emit),这样首次调用耗时与网络无关。
P3(降低默认风险)
product-analytics/src/index.ts:14-17注释明确 "no user settings surface",
enabled是 volatile 配置)。建议:要么做成显式 opt-in,要么在首发版本默认关闭,直到 P2 完成。
welcome-backend.ts:136(analyticsEnabled,也是 1s deadline)存在,这次它 31–48ms 返回没触发,但属于同类隐患。
回归测试建议
在 Desktop 的宿主级测试里固定这个场景:"一条被 abort 的 fire-and-forget RPC 之后,
随后的
settings/describe必须成功"(可用本地 HTTP 服务器故意延迟第一条响应来驱动)。现有
apps/desktop/tests/welcome-host.spec.ts/main-startup.spec.ts可扩展。另一个独立问题(顺带修)
apps/desktop/src/mandatory-update-policy.ts:161-167的策略请求不发送User-Agent,只带 8 个
x-client-*;packages/credentials/deepseek-account/src/client-metadata.ts:37-46的账号/通知请求同样没有 UA。这类"零浏览器特征"的请求在华为云 WAF 规则下会被 429/重置。
虽然它不是本次崩溃的原因,但它是 issue 里被误判为根因的那条请求,建议按要求补上产品标识。
6. 本次本地修复流程(用户侧记录)
6.1 事实取证
D:\deepseek(DeepSeek Harness.exeFileVersion0.2.2-rc.3)。resources/app.asar头部(Python 脚本,只读),读出package.json/dsh/desktop-runtime.json/ bundled@deepseek-ai/dsh清单,确认:https://harness.deepseek.com;dshBuildCommit = 6b729fed...、dshBuildDirty = false、channelnightly;dsh-deepseek-chat-import@0.2.2-rc.3)。crash-*-main.log,无crash-*-host.log(fatal-recovery.ts:70只记录第一条致命错误,Host 侧诊断可能被吞,不能据此判断 Host 存活)。
%USERPROFILE%\.dsh\profiles\desktop:第三方插件已在 22:50/22:56 被"禁用第三方插件并重启"移除,但 23:25/23:26/23:27 仍崩 ⇒ 排除插件。
6.2 复现与定位
~/.dsh的profiles/desktop与storages到工作区副本(不动真实数据);host-process.ts的方式启动 Host(Electron Node 模式 + IPC),执行repro/host-run.mjs:四条欢迎页 RPC 全 200 ⇒ Host 健康;
repro/host-run2.mjs(重放 shell 精确序列):1.013s abort → 9.031s ECONNRESET ⇒ 复现;repro/host-run3.mjs(放开 deadline):首次report11,083ms,第二次 10ms ⇒ 定位到耗时来源。6.3 修复
cordis.patch.yml追加,验证有效(report15ms,全部 200):AppendAllText方式,避免中途 BOM 破坏 YAML):report14ms,settings/describe/llm/listConfigurableProviders/credentials/describe/account/getState全部 200,Hoststderr为空。6.4 生效与还原
6.5 影响与限制
analyticsEnabled);此时请提供新的
crash-*-main.log与repro/host-run2.mjs的输出。附录 A:复现脚本(工作区
repro/)host-run.mjshost-run2.mjsECONNRESEThost-run3.mjsreport的真实耗时(11.1s)关键实现要点(与
host-process.ts一致):RPC 形状与
welcome-backend.ts:52-70一致:附录 B:关键文件与行号索引
apps/desktop/src/main.tstrack():埋点上报,异常被吞apps/desktop/src/main.tsvoid track('desktop_app_launch')apps/desktop/src/main.tsreconcileBackend()(含openInitialWindow)apps/desktop/src/main.tsreportFatal(error, 'main')apps/desktop/src/main.tsopenInitialWindow();1457readWelcomeState()apps/desktop/src/main.tsphase/backendReadyapps/desktop/src/welcome-backend.tsanalyticsEnabled()/report()的 1s deadlineapps/desktop/src/welcome-backend.tssend = fetch,invoke()无 deadlineapps/desktop/src/backend-controller.tsreadyapps/desktop/src/host-process.tsapps/desktop/src/fatal-recovery.tspackages/client/product-analytics/src/index.tsreport()首次 11s 的实现packages/host/product-telemetry-otel/src/index.tsapps/desktop/src/mandatory-update-policy.tspackages/bundle/web-app/cordis.patch.ymlproduct-analytics的enabled: true来源All reactions