DeepSeek Harness 打开方式列表错乱、present.open 500 怎么修
DeepSeek Harness(DSH)在 Windows 上的「打开方式」有两个看起来不相干、其实共用一条代码路径的故障:点文件卡片时接口直接 500,或者列表里塞满无关程序、选中后整个助手进程挂住。 两种都不是文件坏了,而是 file-applications-windows 在枚举注册表 handler 时既没有逐项容错,也没有正确设置过滤位。这篇先帮你判断遇到的是哪一种,再给三条可以分开使用的修法,最后用一组文件把修复验证完整。
DeepSeek Harness 打开方式故障:先分清 500 与列表错乱
一句话结论:present.open 恒 500 是「枚举过程中被一个坏 handler 打断」,列出无关程序是「枚举时没有过滤」,前者要加逐项容错,后者要改过滤位,两者可以同时存在但修法不同。 先把症状对到代码路径上:
| 你看到的现象 | 对应位置 | 性质 |
|---|---|---|
点卡片就 500,改用 action=open 的 POST 能打开 | file-applications-windows.ts:202-204 | 名字查询无 try,单项失败中止全表 |
| 列表里出现 Premiere、记事本等无关程序 | file-applications-windows.ts:181-185 | SHAssocEnumHandlers 过滤位为 0 |
| 选中无关程序后 UI 停在「正在打开…」 | 被选中的 handler 自身挂起 | 不在 DSH 进程内,但会留下僵尸脚本 |
.cmd 的列表为空 | 过滤位改成 1 后没有空集兜底 | 需要 fallback 到默认动词 |
据讨论里的用户实测,present.open 的 GET 与 POST 走的是同一份 handler 枚举结果,所以只要枚举阶段抛错,GET 一定 500,而 POST 直接调 ShellExecute 不受影响(#10322)。其他 Windows 平台问题可以先对照 Windows 平台常见错误。
DeepSeek Harness 打开方式为什么会坏
一句话结论:500 来自枚举循环里对 GetName / GetUIName 没有逐项捕获,列表脏来自 SHAssocEnumHandlers 的过滤位传了 0;两者都出在同一个枚举文件里。 同一个文件里取图标的 :163-165 已经做了逐项 catch,名字查询这一段漏了,属于典型的「同一份数据、两处消费、只防了一处」。
根因一:一个坏 handler 打断整次枚举
触发条件很普通:装过又卸载的编辑器、绿色版工具、损坏的 ProgID,都会在注册表里留下指向不存在 exe 的 handler。枚举到它时 COM 返回 E_FAIL,循环没有 catch,异常一路传到 HTTP 层,整张列表作废。
根因二:过滤位把历史注册项都带出来
过滤位传 0 时,SHAssocEnumHandlers 返回的是「该扩展名注册过的全部 handler」,不是「能打开它的程序」。 注册表并不区分「曾经声明过支持」和「现在仍能处理」,只要装过就会留记录。更麻烦的是选中之后的挂起:不兼容的程序可能在启动时等待一个永远不会来的输入,DSH 的启动监控等不到退出码,UI 就停在「正在打开…」。
怎么确认是过滤位而不是注册表污染
- 在 PowerShell 里跑
Get-ItemProperty "Registry::HKEY_CLASSES_ROOT\.cmd\OpenWithProgids",数 ProgID 数量;预期能看到该扩展名登记的全部候选。 - 如果数量远大于你预期的「记事本 + 命令提示符」,基本可以判定是过滤位为 0 把历史注册项都带出来了;预期这一步就能把范围缩到枚举层。
- 临时把过滤位改成 1,重启 DSH 后再看同一列表;预期无关项消失,若消失就不需要动注册表。
DSH 打开方式错误的三种修法
一句话结论:能改源码就优先「逐项容错 + 过滤位」一起做;不能改源码就清注册表;只想先止血就把过滤位改成 1 并加空集 fallback。
修法 A:把名字查询包进 try,失败项跳过
这是改动最小的做法,适合你能改源码、但不想动枚举范围的情况。核心是「一条坏记录不应该让整张表消失」。
- 打开
packages/util/native-command/src/file-applications-windows.ts,定位:202-204的GetName/GetUIName调用;预期能看到两处连续的名字查询。 - 把两次调用包进
try/catch,catch 里continue到下一个 handler,不要return;预期坏项被跳过,循环继续。 - 在 catch 里记一条
logger.warn,带上 handler 的 CLSID 或可执行路径;预期日志能定位到具体注册项。 - 重新构建并启动 DSH,点同一个文件卡片;预期接口返回 200,列表里只剩可用项。
修法 B:改过滤位,只枚举推荐程序
如果你更在意「列表干净」而不是「列出所有可能」,直接把过滤位从 0 改成 1。 这样 Shell 只返回标记为推荐的 handler,Premiere 这类只是「注册过」的程序不会出现。
- 在同一文件
:181-185找到SHAssocEnumHandlers(extension, 0, ...);预期第二个参数当前是0。 - 把第二个参数改成
1(ASSOC_FILTER_RECOMMENDED);预期枚举结果只保留推荐 handler。 - 对
.cmd、.bat这类可能返回空集的扩展名,在结果为空时回退到ShellExecute的默认动词;预期空列表不会直接展示给用户。 - 用
.txt、.cmd、无默认程序的扩展名各试一次;预期三种都有合理结果,不再是空对话框。
修法 C:清理注册表里的坏 handler
不想改代码时,可以把指向不存在 exe 的注册项清掉,让枚举自然变干净。 这条适合企业环境或临时救急,但要注意备份。
- 在
HKEY_CLASSES_ROOT\<扩展名>\OpenWithProgids下逐项核对 ProgID;预期列出所有候选,先不动系统自带项。 - 对每个 ProgID,到
HKEY_CLASSES_ROOT\<ProgID>\shell\open\command看默认值指向的 exe 是否还存在;预期能标出指向空路径的坏项。 - 把坏 ProgID 导出备份后删除,只删
OpenWithProgids里的引用;预期资源管理器的双击行为不受影响。 - 重启资源管理器(
taskkill /f /im explorer.exe && start explorer.exe),再回 DSH 点文件卡片;预期列表变短且不再 500。
如果三种都想落地,建议顺序是「先容错止血 → 再改过滤位 → 最后按需清注册表」,这样每一步都能单独验证。只想马上能用就选修法 A,列表脏得厉害再加 B,企业机器不方便改代码才用 C。
DSH 插件修复后怎么验证与避坑
一句话结论:用「正常文件 + 脚本文件 + 无默认程序文件 + 故意损坏的 handler」四组样本,才能同时验证列表、启动和容错三条路径。 具体步骤:
- 准备
note.txt、task.cmd、一个没有默认程序的.foo文件,以及一个把shell\open\command指向不存在 exe 的测试 ProgID;预期四类样本覆盖正常、脚本、空集和坏项。 - 在 DSH 里依次点这四个文件的卡片;预期前三个能正常出列表,第四个不会让接口 500。
- 对
.cmd选一个非默认程序;预期 UI 不再永久停在「正在打开…」,若仍挂起说明该 handler 自身有问题。 - 打开任务管理器;预期没有残留的
associations.ps1或孤立的编辑器进程。 - 重启 DSH 再重复第 2 步;预期修复在新进程里依然生效。
避坑要点:
- 不要为了「列表干净」直接删
HKEY_CLASSES_ROOT下的系统 ProgID,只删OpenWithProgids里的引用,否则会影响资源管理器自身的双击行为。 - 过滤位改成 1 之后,部分老程序不会再出现在列表里,这是预期行为;如果用户明确需要它,用「浏览其他程序」路径手动选择。
- 逐项容错只解决「枚举阶段」的失败;被选中的 DSH插件 自身挂起时,仍需在任务管理器里结束它,DSH 侧能做的是加启动超时和失败提示。
- 这两个问题在 0.2.1-rc.2 上仍可复现,讨论里没有给出已合入的版本号;如果你看到修复,以实际 release notes 为准。
DeepSeek Harness 打开方式常见问题
Q:DeepSeek Harness 的 present.open 一直 500,但 POST 能打开,为什么? A:DeepSeek Harness 的 present.open 返回 500 说明枚举 handler 阶段失败,而不是文件路径坏了。GET 和 POST 共用同一份枚举结果,POST 直接调 ShellExecute 所以不受影响;把名字查询包进 try 就能恢复列表。
Q:DeepSeek Harness 打开方式列出 Premiere、记事本等无关程序,选中后卡死怎么办? A:DeepSeek Harness 的打开方式列表变脏,是因为 SHAssocEnumHandlers 的过滤位传成了 ASSOC_FILTER_NONE。把过滤位改成 1,并在结果为空时回退到默认动词,无关项就会消失;被选中的程序若仍挂起,用任务管理器结束它。
Q:DeepSeek Harness 里 .cmd 的打开方式列表为空,删注册表也没用,先查什么? A:DeepSeek Harness 里 .cmd 列表为空,应先查过滤位和空集 fallback,而不是先删注册表。过滤位设为 1 时部分系统会返回 0 个候选,必须回退到 ShellExecute 的默认动词;确认是空集后再检查 OpenWithProgids 是否被写脏。
Q:DeepSeek Harness 打开方式修好后,怎么验证不会再拖死助手进程? A:DeepSeek Harness 的修复要用四类文件验证:纯文本、.cmd 脚本、没有默认程序的扩展名,以及一个故意损坏的 handler。每类都确认列表能出、选中能起、取消能退,并且任务管理器里不残留 associations.ps1。
常见问题
DeepSeek Harness 的 present.open 返回 500 说明枚举 handler 阶段失败,而不是文件路径坏了。GET 和 POST 共用同一份枚举结果,POST 直接调 ShellExecute 所以不受影响;把名字查询包进 try 就能恢复列表。
DeepSeek Harness 的打开方式列表变脏,是因为 SHAssocEnumHandlers 的过滤位传成了 ASSOC_FILTER_NONE。把过滤位改成 1,并在结果为空时回退到默认动词,无关项就会消失;被选中的程序若仍挂起,用任务管理器结束它。
DeepSeek Harness 里 .cmd 列表为空,应先查过滤位和空集 fallback,而不是先删注册表。过滤位设为 1 时部分系统会返回 0 个候选,必须回退到 ShellExecute 的默认动词;确认是空集后再检查 OpenWithProgids 是否被写脏。
DeepSeek Harness 的修复要用四类文件验证:纯文本、.cmd 脚本、没有默认程序的扩展名,以及一个故意损坏的 handler。每类都确认列表能出、选中能起、取消能退,并且任务管理器里不残留 associations.ps1。
相关术语
- present.open
- present.open 是 DeepSeek Harness 用于「把工作区文件交给系统默认程序打开」的接口,GET 形态负责枚举可用程序,POST 形态负责真正启动。它的 handler 列表来自 Windows 注册表与 COM,因此任何一个损坏的注册项都可能影响整个接口。— https://github.com/deepseek-ai/deepseek-harness/discussions/10322
- SHAssocEnumHandlers
- SHAssocEnumHandlers 是 Windows Shell 提供的按扩展名枚举关联处理程序的 API,第二个参数是过滤位,0 表示不过滤、1 表示只返回推荐程序。传 0 会把所有「曾经注册过」的程序都列出来,包括并不接受该类型的程序。— https://github.com/deepseek-ai/deepseek-harness/discussions/10349
- COMException E_FAIL
- COMException E_FAIL 是 COM 调用返回的通用失败码 0x80004005,表示被调用方没有给出更具体的原因。在文件关联场景里,它通常意味着注册表指向的 exe 已卸载、DLL 注册损坏,或该 handler 在当前用户权限下无法初始化。— https://github.com/deepseek-ai/deepseek-harness/discussions/10322
来源
- #10322 — present.open 返回 500:单个文件关联 handler 失败即让整个「打开方式」列表失效· deepseek-ai(GitHub Discussions)
- #10349 — 「打开方式」列出所有已注册处理器,选中非适用程序后助手进程永久挂起· deepseek-ai(GitHub Discussions)
- DeepSeek Harness 官方仓库· deepseek-ai(GitHub)