IDBots v0.8.0:升级后「一发消息宿主即崩」(EXC_BREAKPOINT / libuv-worker)
可直接作为 issue 正文提交。证据等级已逐条标注:[已证实] / [强关联·未证实] / [未核实]。
环境
| 项 |
值 |
| IDBots |
0.8.0(build 0.8.0),commit f83b886e = 官方 tag v0.8.0 |
| 上游 |
metaid-developers/IDBots |
| 系统 |
macOS 26.6.2 (25G83),arm64 |
| 内嵌运行时 |
@deepseek-ai/dsh 0.1.5-rc.2(Contents/Resources/dsh-runtime) |
| 发生时间 |
2026-09-12 |
| 升级路径 |
v0.7.0 → v0.8.0 |
症状
启动正常,界面正常。在既有会话里发一条消息 → 宿主主进程立刻崩溃退出(macOS 弹出「IDBots 意外退出」)。新建会话未观察到崩溃。
崩溃签名(4 次复现,完全一致)[已证实]
bug_type=309,exception.type=EXC_BREAKPOINT,signal=SIGTRAP,termination.indicator="Trace/BPT trap: 5"
- 故障线程名
libuv-worker(线程池工作线程);主线程当时正常停在 AppKit 事件循环
- 故障帧
frame[0].imageOffset = 0x5500dc8(Electron Framework)—— 4 次崩溃完全相同,即确定性崩溃点
- 故障指令
atPC 首条为 0xD4200000(brk #0)→ 原生层主动陷阱,不是内存错误、不是 JS 异常
- 崩溃进程中未加载任何第三方
.node 原生插件(koffi/sharp/node-pty 均属 dsh-runtime 子进程)
- 诊断字段
asi / abort_cause / is_js_error 全部缺失;ELECTRON_ENABLE_LOGGING=1 捕获的 stderr、系统统一日志、应用 main.log 三处均零输出
复现条件
- 机器上存在 0.8.0 之前版本的会话数据
- 其中至少一个会话在旧内核上被 steer 或 cancel 过(该轮
turn/end 的 abort cause 会被写成裸字符串)
- 打开该会话并发一条消息
→ 命中率很窄,因此社区几乎无人碰到、也就无人上报。
证据链
-
崩溃报告:4 份 .ips 同签名、同一故障偏移 [已证实]
-
logs/cowork.log 停在固定位置:Resolved API config → memory:promptBlocks → DSH reasoning effort 之后再无输出;应用 main.log 同样在 [GigSquare Rating] updated 0 services 后戛然而止 [已证实]
-
预热失败垫刀:每次启动 prewarmDshRuntime 都失败——
[WARN] [prewarmDshRuntime] Warmup failed; first turn will cold-start
error: Provider 'deepseek' does not offer enabled model 'deepseek-v4-flash-vision-exp';
provider selection is required.
于是走冷启动,把清扫推迟到首轮发言才执行,这正是"一发言就崩"的由来 [已证实]
-
清扫「做了一半」的量化铁证 [已证实]:
backups/v0-abort-cause/ 下每个会话产物备份的创建时刻呈两段式分布:
| 备份时刻 |
份数 |
归属 |
14:56 |
36 |
宿主那趟(崩死前做到的) |
15:14 / 15:15 |
42 + 56 = 98 |
事后用系统 Node 补跑完成的剩余部分 |
| 合计 |
134 |
36 + 98 |
而补跑那趟的返回值是 {"scanned":806,"patched":98,...} —— 补跑的 98 与备份新增的 98 完全吻合。
即:宿主那趟只在 36 个会话上写出了备份就死了,剩下 98 个从未被处理。
-
marker 缺失 [已证实]:
- marker
dsh-sessions/v0/.v0-abort-cause-sanitized 在补跑前全盘搜索不存在(ls 明确返回 No such file or directory)
- 按
sanitize-v0-abort-cause.mjs 自身逻辑:marker 不在 = 这一趟从未完成,宿主下次启动会全量重跑
-
时间吻合 [已证实]:最早的备份目录时间戳为 09-12 14:56,首次崩溃为 14:56:12 —— 同一分钟
根因判断
已证实部分:v0.8.0 引入的一次性清理 sanitize-v0-abort-cause.mjs(位于 dshKernel.ensureRuntime,在明文→zstd 编码迁移之后执行)从未跑完,marker 未落地,宿主每次冷启动都重跑,并在首轮发言时崩溃。
强关联、尚未证实部分:崩溃发生在该清扫内部。
⚠️ 反证:把同一份 sanitize-v0-abort-cause.mjs 用系统 Node v24.12.0 单独执行,跑得干干净净、零崩溃:
{"scanned":806,"patched":98,"patchedEvents":253,"skipped":false}
因此"崩在清扫里"目前只有时间与调用链的关联,没有函数级证据——详见下方"未核实"。
已实测有效的绕过方式
App 未运行时,用系统 Node 单独执行该清扫使其落 marker:
// node --input-type=module
const m = await import('<IDBots.app>/Contents/Resources/dsh-runtime/lib/sanitize-v0-abort-cause.mjs')
await m.sanitizeV0AbortCauses('<userData>/dsh-sessions/v0', { log: console.log })
结果:marker 写入 {"version":1,"scanned":806,"patched":98,"patchedEvents":253,...};二次运行返回 {"skipped":true}。
宿主重启后发消息不再崩溃,验收证据:DiagnosticReports 无新崩溃文件、main.log/cowork.log 继续正常推进。
给上游的修复建议
- 清扫必须可断点续跑。当前 marker 只在整趟扫完后一次性写入,任何中途失败(含进程死亡)都会导致每次启动重跑全量 806 个会话。建议改为 per-session 标记,或把已处理会话记入一个可增量提交的清单。
- 清扫不应在
ensureRuntime 的关键路径上阻塞。它属于一次性数据迁移,应异步化,且失败不应影响运行时启动。
- 补诊断打点:清扫入口/出口、每个会话处理前后、marker 写入前后各打一条日志。本次排查的最大障碍恰恰是"崩溃零输出",若有这些打点,定位时间可从数小时降到数分钟。
prewarmDshRuntime 遇到已退休模型应自动折叠为当前模型(src/renderer/config.ts 里已有 'deepseek-v4-flash-vision-exp' → DEEPSEEK_DEFAULT_MODELS[0] 的别名映射),而不是 WARN 后放弃预热——预热失败把清扫挤到"首轮发言",直接改变了故障的表现形态。
- 崩溃可观测性:考虑启用 Crashpad 或给主进程装
uncaughtException/崩溃回调落盘,当前 4 份 .ips 全部无 asi、无 abort 原因,排障成本极高。
未核实 / 不确定性(不掩盖)
[未核实] 崩溃具体断在哪一行原生代码。Electron Framework 无符号表,.ips 中所有帧的符号解析均不可信(ares_dns_rr_get_ttl+巨大偏移),asi 字段缺失。
[未核实] 清扫在纯 Node 下可跑通、在 Electron 内会崩——若二者确有差异,方向应转向「宿主 Node 与系统 Node 在该 zstd/线程池路径上的行为差异」,而非清扫逻辑本身。
[未核实] 是否还有第二处引用旧模型 id。本次仅修掉配置目录中的死条目,路由逻辑本身未动。
- 本次修复动作只做了两步:①用系统 Node 跑完清扫落 marker;②把
app_config.providers.commandcode.models[] 里已退休的 deepseek/deepseek-v4-flash-vision-exp 改名为 deepseek/deepseek-v4.1-flash。未修改任何源码,未向仓库提交任何内容。
IDBots v0.8.0:升级后「一发消息宿主即崩」(EXC_BREAKPOINT / libuv-worker)
环境
f83b886e= 官方 tag v0.8.0metaid-developers/IDBots@deepseek-ai/dsh0.1.5-rc.2(Contents/Resources/dsh-runtime)症状
启动正常,界面正常。在既有会话里发一条消息 → 宿主主进程立刻崩溃退出(macOS 弹出「IDBots 意外退出」)。新建会话未观察到崩溃。
崩溃签名(4 次复现,完全一致)
[已证实]bug_type=309,exception.type=EXC_BREAKPOINT,signal=SIGTRAP,termination.indicator="Trace/BPT trap: 5"libuv-worker(线程池工作线程);主线程当时正常停在 AppKit 事件循环frame[0].imageOffset = 0x5500dc8(Electron Framework)—— 4 次崩溃完全相同,即确定性崩溃点atPC首条为0xD4200000(brk #0)→ 原生层主动陷阱,不是内存错误、不是 JS 异常.node原生插件(koffi/sharp/node-pty 均属dsh-runtime子进程)asi/abort_cause/is_js_error全部缺失;ELECTRON_ENABLE_LOGGING=1捕获的 stderr、系统统一日志、应用main.log三处均零输出复现条件
turn/end的 abort cause 会被写成裸字符串)→ 命中率很窄,因此社区几乎无人碰到、也就无人上报。
证据链
崩溃报告:4 份
.ips同签名、同一故障偏移[已证实]logs/cowork.log停在固定位置:Resolved API config→memory:promptBlocks→DSH reasoning effort之后再无输出;应用main.log同样在[GigSquare Rating] updated 0 services后戛然而止[已证实]预热失败垫刀:每次启动
prewarmDshRuntime都失败——于是走冷启动,把清扫推迟到首轮发言才执行,这正是"一发言就崩"的由来
[已证实]清扫「做了一半」的量化铁证
[已证实]:backups/v0-abort-cause/下每个会话产物备份的创建时刻呈两段式分布:14:5615:14/15:15而补跑那趟的返回值是
{"scanned":806,"patched":98,...}—— 补跑的 98 与备份新增的 98 完全吻合。即:宿主那趟只在 36 个会话上写出了备份就死了,剩下 98 个从未被处理。
marker 缺失
[已证实]:dsh-sessions/v0/.v0-abort-cause-sanitized在补跑前全盘搜索不存在(ls明确返回 No such file or directory)sanitize-v0-abort-cause.mjs自身逻辑:marker 不在 = 这一趟从未完成,宿主下次启动会全量重跑时间吻合
[已证实]:最早的备份目录时间戳为09-12 14:56,首次崩溃为14:56:12—— 同一分钟根因判断
已证实部分:v0.8.0 引入的一次性清理
sanitize-v0-abort-cause.mjs(位于dshKernel.ensureRuntime,在明文→zstd 编码迁移之后执行)从未跑完,marker 未落地,宿主每次冷启动都重跑,并在首轮发言时崩溃。强关联、尚未证实部分:崩溃发生在该清扫内部。
已实测有效的绕过方式
App 未运行时,用系统 Node 单独执行该清扫使其落 marker:
结果:marker 写入
{"version":1,"scanned":806,"patched":98,"patchedEvents":253,...};二次运行返回{"skipped":true}。宿主重启后发消息不再崩溃,验收证据:
DiagnosticReports无新崩溃文件、main.log/cowork.log继续正常推进。给上游的修复建议
ensureRuntime的关键路径上阻塞。它属于一次性数据迁移,应异步化,且失败不应影响运行时启动。prewarmDshRuntime遇到已退休模型应自动折叠为当前模型(src/renderer/config.ts里已有'deepseek-v4-flash-vision-exp' → DEEPSEEK_DEFAULT_MODELS[0]的别名映射),而不是 WARN 后放弃预热——预热失败把清扫挤到"首轮发言",直接改变了故障的表现形态。uncaughtException/崩溃回调落盘,当前 4 份.ips全部无asi、无 abort 原因,排障成本极高。未核实 / 不确定性(不掩盖)
[未核实]崩溃具体断在哪一行原生代码。Electron Framework 无符号表,.ips中所有帧的符号解析均不可信(ares_dns_rr_get_ttl+巨大偏移),asi字段缺失。[未核实]清扫在纯 Node 下可跑通、在 Electron 内会崩——若二者确有差异,方向应转向「宿主 Node 与系统 Node 在该 zstd/线程池路径上的行为差异」,而非清扫逻辑本身。[未核实]是否还有第二处引用旧模型 id。本次仅修掉配置目录中的死条目,路由逻辑本身未动。app_config.providers.commandcode.models[]里已退休的deepseek/deepseek-v4-flash-vision-exp改名为deepseek/deepseek-v4.1-flash。未修改任何源码,未向仓库提交任何内容。