Repository navigation
[Bug][Desktop][macOS] rc.5:profile patch 被重写时丢失手写的 insert 条目,MCP 服务器静默消失 #12191
【缺陷报告】DSH 重写 profile patch 时静默丢弃手写的
|
| 项 | 值 |
|---|---|
| 应用 | @deepseek-ai/dsh-desktop 0.2.1-rc.5(/Applications/DeepSeek Harness.app) |
| 系统 | macOS 27(darwin arm64) |
| Electron / Node | 44.7.0 / 24.21.0 |
| 界面语言 | zh-CN |
| 配置文件 | ~/.dsh/profiles/desktop/cordis.patch.yml |
| 相关插件 | @deepseek-ai/dsh-mcp-client(包内存在,版本随安装) |
三、现象
- 会话里调用 MCP 工具,全部返回
unknown tool:Error: unknown tool "mcp__iphone__status" ps aux | grep mcp_server.py没有子进程;pgrep -f "--mcp"也为空;- 重启应用 / 重启系统 / 反复重启 MCP 均无效;
- 崩溃日志(
~/Library/Logs/DeepSeek Harness/crash-*.log)里没有任何 MCP 相关记录; - 手工用 stdio 直连同一命令,
initialize+tools/list立刻返回 39 个工具——服务本身完全正常。
四、时间线与证据
本机的真实事故(时间均为本地时区):
| 时间 | 事件 | 证据 |
|---|---|---|
| 11-04 16:48–16:50 | 启动连续崩溃 5 次(与本缺陷无关的 cookie 堆积问题,另案) | 5 份 crash-*.log |
| 16:50:42 | 点击崩溃对话框的「禁用第三方插件、备份 profile patch 并重启」 | 生成 cordis.patch.yml.bak-1793782242745(文件名的毫秒时间戳正好是 16:50:42) |
| — | 该备份内容 1394 B,包含完整的 - insert: iphone-mcp 段 |
见 §五 |
| 17:24 | 清 cookie 后应用成功启动 | cordis.patch.yml 的 mtime |
| 17:24 | 同一文件变成 661 B,insert 段消失,只剩 3 条 UI 插件配置 |
wc -c / grep |
| 18:54 | 首次调用 MCP 工具 | unknown tool |
关键点:备份与当前文件的差集,正好就是那段 - insert:。也就是说
恢复动作"备份了旧文件、然后写入了不含用户条目的新文件",用户条目被静默丢弃。
五、配置片段(README 可复现的最小样例)
崩溃前(备份里,1394 B):
- id: ui-chat
name: "@deepseek-ai/dsh-client-ui-chat"
config: { transcriptView: standard }
# …
- insert:
- id: iphone-mcp
name: "@deepseek-ai/dsh-mcp-client"
config:
serverName: iphone
transport: stdio
command: /Users/jianghui/Documents/XIAO ESP32S3 Plus/.venv/bin/python
args:
- /Users/jianghui/Documents/XIAO ESP32S3 Plus/control/mcp_server.py
env:
HIDBRIDGE_BASE: http://127.0.0.1:8890
failOnStartupError: true
toolCallTimeoutMs: 60000崩溃恢复后(661 B):上面 - insert: 整段不存在,其余 3 条 UI 条目保留。
六、建议的复现步骤(给开发)
- 在
cordis.patch.yml末尾加一条无害的- insert:(任意插件 id); - 记录文件大小与
grep -c "insert"; - 触发一次「禁用第三方插件、备份 profile patch 并重启」(或任何会重写该 patch 的路径);
- 对比:
insert条目是否还在;若不在,观察:- 有没有任何日志 / 弹窗 / 设置页提示;
.bak-*备份是否生成(本机生成了,说明备份逻辑存在,但写入是覆盖式而非合并式)。
七、根因分析(推断与依据)
- 依据 1:备份文件(1394 B)与事故后文件(661 B)的差异恰好是用户的
insert条目; - 依据 2:备份文件名是毫秒时间戳,指向恢复动作的执行时刻(16:50:42);
- 依据 3:正常启动不会重写该文件——同样的
insert段从安装期一直存活到事故当天(多日、多次启动); - 推断:恢复/设置写入路径用「按内部模型重建整份 patch」的方式写文件(replace),
只序列化它认识的条目;无法识别的insert列表被丢弃,且没有 diff 校验、没有告警。
请重点检查 cordis.patch.yml 的全部写入点(恢复对话框、设置保存、插件启停),
确认哪些是 replace、哪些是 merge。
八、影响
- 影响面:所有在 profile patch 里手写
insert的用户——MCP 服务器、自定义插件条目都算;
而当前 build 没有 MCP 设置界面(包内只有dsh-mcp-client/dsh-mcp-resources,
没有ui-settings-mcp),所以 MCP 只能手写在 patch 里,等于全部用户都在风险面内; - 严重度:配置静默丢失 + 无任何提示 + 重启不恢复 + 只有改文件才能救;
- 可发现性:极差。用户看到的是「工具没了」,会先怀疑模型、网络、MCP 服务端,
实际是本地配置被吃掉了。本机从首次报错(18:54)到定位(00:55)跨了约 6 小时、一次会话重启。
九、建议修复
A(必须):重写时合并而不是覆盖
读取现有文件 → 按 id 合并内部模型 → 保留所有无法识别的条目 → 再写回。
若实现成本高,至少对 insert 列表做「原样保留」这一条。
B(必须):写入前后做 diff 校验
新内容的条目数/条目集合若是旧内容的真子集,视为异常:要么保留旧条目,要么中止写入并报错。
最省事的实现:写前 cp 一份 .bak-<ts>(本机恢复动作已有此行为),
写后比较 insert 条数,少了就回滚 + 记录一条 WARN。
C(强烈建议):让"0 个 MCP 服务器"可见
启动时若 mcpServers 为空,在日志与设置页明确显示「未配置任何 MCP 服务器」。
现在的行为是"什么都没发生",这是本缺陷最难查的部分。
failOnStartupError: true 只能覆盖「spawn 失败」,覆盖不了「条目根本不存在」。
D(建议):给 MCP 一个正式设置页
现在用户必须自己发现 cordis.patch.yml 在哪、格式是什么,还要自己承担被重写覆盖的风险。
十、临时缓解(用户侧,已验证)
# 检查
grep -q iphone-mcp ~/.dsh/profiles/desktop/cordis.patch.yml || echo "MCP 配置丢了"
# 补回(先备份,再追加,最后做 YAML 校验;不合法自动回滚)
tools/ensure-dsh-mcp.sh脚本(本机自用,无公开仓库):核心就四步——检测关键字 → cp 备份成 .bak-<epoch> →
追加模板段 → yaml.safe_load 校验,不合法就 cp 回滚。恢复后需重启宿主才生效。
十一、附:另一个独立缺陷
本次事故的诱因是启动崩溃(dsh-auth-* cookie 无限累积 → 请求头超 16 KiB → 431 →
desktop welcome: Web authentication failed),已另行整理,与本缺陷无因果关系:
即使没有那次崩溃,只要用户点一次恢复动作,配置就会丢。
—— 提交者:macOS 用户,DSH Desktop 0.2.1-rc.5
Replies: 2 comments 3 replies
|
对照了 rc.5 的源码,补充一个和修复方向有关的细节:恢复动作没有解析 YAML 再筛掉 这能解释备份中的 恢复方案的设计记录 提到,坏掉的 patch 或其中插入的插件本身可能导致启动失败,所以恢复时把整个 patch 移出启动路径。也就是说,直接自动合并所有旧条目可能会把导致崩溃的配置带回来;不过,恢复后让用户更容易发现和找回配置,确实值得改进。 你已经从备份找回了 MCP 配置;如果之前还通过其他 bundle 启用了第三方插件,也可以检查一下它们的启用状态。另外, 以上是按 rc.5 源码和现有测试核对的,没有在你的桌面环境里复现。 |
|
核对过源码与设计记录后,结论是:这不是「重写 patch 时漏掉 insert」,而是崩溃恢复动作的既定行为,但你的观察揭示了一个真实的可用性缺口。 机制(rc.5 源码路径)
立刻恢复的办法 ls -l ~/.dsh/profiles/desktop/cordis.patch.yml.bak-*
# 方式一:只把备份里的 - insert: 段追加回当前文件
# 方式二:若事故后没有再改过 UI 设置,直接整份覆盖回去
cp ~/.dsh/profiles/desktop/cordis.patch.yml.bak-<时间戳> ~/.dsh/profiles/desktop/cordis.patch.yml
# 重启 DSH(注意:整份覆盖会丢掉恢复后新增的 3 条 UI 条目;只拷 文档与真正的缺口 更值得修的是产品面:恢复成功后应用内应有一次可见提示(备份路径 + 已停用内容 + 一键查看/恢复备份),而不是只写 Electron console;或者在执行恢复动作前明确告知「手写条目会失效」。源码上这需要在恢复时写一个 marker,下次启动 ready 后消费一次并展示。若维护者认可这个方向,我可以出 patch 草案(marker + 一次性通知 + Plugins 页入口);仓库 PR 通道关闭,只能分支形式供 cherry-pick。 |
手动恢复那一步补个边界情况。先退出 DSH 再编辑配置。如果重启后还没保存过设置,活动 patch 可能仍是带注释的
[]空模板,这时需要把[]替换成备份里的- insert:列表。直接在文件末尾追加会让 YAML 解析失败。当前文件已有- id:列表时,可以把- insert:放在同一顶层列表里。我用 rc.5 的解析函数和锁定的 js-yaml 4.3.2 做了内存对照。空模板后直接追加会报
end of the stream or a document separator is expected,替换[]和往已有列表追加都能解析。恢复说明里可以带上这个细节。空模板 · 解析入口