DeepSeek Harness 报 "reading 'prepare'" 错误?插件重复核心包排查
DeepSeek Harness 报 Cannot read properties of undefined (reading 'prepare') 且所有工具调用都失败,根因通常是 profile 里出现了第二份 @deepseek-ai/dsh-tools——工具调度器用模块内 Symbol() 做键,两份副本键不相等,查回来就是 undefined。 一条 find 命令就能定位,清理后即恢复;但已遗留 tool_calls 的会话救不回来,只能放弃。
DeepSeek Harness 报 reading 'prepare' 错误长什么样
报错固定为 UNKNOWN: Cannot read properties of undefined (reading 'prepare'),发生在每个工具调用上,且加载时不报任何错。 有用户实跑遇到(讨论原文),现象如下:
- profile 启动正常,但让 Agent 调用任何工具都抛
Cannot read properties of undefined (reading 'prepare'); - 报错不点名是哪个工具、也不点名是哪个包重复——用户第一反应通常是怀疑框架或模型提供方坏了;
- 更麻烦的是二次伤害:失败的 turn 把
tool_calls写进了日志却没有对应结果,下一轮请求直接被模型提供方拒绝(An assistant message with 'tool_calls' must be followed by tool messages...),整个会话不可恢复。
这个报错之所以难查,是因为问题藏在依赖解析里,而不是运行时逻辑里:TOOL_RUNTIME_SCHEDULER 是一个模块内 Symbol()(dsh-tools 定义处),Symbol() 的身份按模块实例隔离,读方和写方必须解析到同一个 dsh-tools 文件。profile 里一旦多出一份副本,两边用的键就不再相等(来源)。
DSH 插件重复核心包:为什么一个包会装出两份
第二份核心包有三个来源:插件把核心包声明为直接依赖、tarball 打包带出传递副本、早期安装遗留的 node_modules。 逐个说明:
- 插件把
@deepseek-ai/*声明成直接依赖:正确做法是放进peerDependencies,依赖树只解析一份;写成dependencies就会在 profile 里装出第二份; - 打包 tarball 安装:把 dsh 本体以 packed tarball 装进 profile 时最容易带出重复的传递副本,尤其当某个包声明了过宽的 peer 范围;
- 遗留的 node_modules:最隐蔽的一种——报告者的 headless profile 依赖全空、问题插件也不在 bundles 里,只是早期安装残留了一棵
node_modules/@deepseek-ai/树,工具调用照样每轮失败。把残留目录移开后立即恢复(mv ~/.dsh/profiles/headless/node_modules/@deepseek-ai /tmp/backup/)。
所以「卸载插件」并不够——只要 node_modules 里还躺着副本,profile 就一直坏着,而且没有任何配置能提示你原因。
怎么排查重复核心包:一条 find 命令
定位方法是一条 find:profile 的 node_modules 里不应该解析出任何 @deepseek-ai 核心包——核心来自 CLI 自己的依赖树,profile 只该放插件。 按步骤查:
- 找出 profile 目录,确认目标 profile 名:
ls ~/.dsh/profiles/
- 检查 dsh-agent 副本,应无任何输出:
find ~/.dsh/profiles/<name>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-agent' -print
- 再查 dsh-tools,应同样无输出:
find ~/.dsh/profiles/<name>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-tools' -print
- 有输出 = 装了两份:把找到的目录记下来,删掉或移到备份目录:
mv ~/.dsh/profiles/<name>/node_modules/@deepseek-ai /tmp/dsh-backup/
- 重启 dsh(
Ctrl+C后重新dsh web),新会话里让 Agent 调用一次工具——不再报reading 'prepare'即修复。
报告者实测:移动整个
node_modules/@deepseek-ai目录(而非逐个包)即可一次清干净,同一命令换dsh-agent/dsh-tools重复执行确认无残留。
修复与预防:清理重复核心包与 DSH 插件依赖规范
修复方向分两层:官方把键改成跨副本一致的 Symbol.for()(配合协议版本守卫),插件作者把核心包放 peerDependencies。 社区已确认根因并给出建议(来源):
- 键改成
Symbol.for('@deepseek-ai/dsh-tools.scheduler'):让两份副本算出同一个键,硬失败降级为"仅冗余安装"; - 加协议版本守卫:
Symbol.for只是解决了访问问题,不同版本的副本会共享同一个键——如果调度器形状跨版本变化,响亮崩溃会变成静默错配,所以注册方要盖版本戳、读方校验并报出双方版本号; - 加载时大声失败:组合 profile 时对每个
@deepseek-ai/*包做 realpath 校验,一个包解析出多个路径就直接报错,而不是等运行时炸; - 插件作者契约:dsh-context 是正确范例(
dependencies: {},所有@deepseek-ai/*与 react/zod 放peerDependencies),官方文档应把这条写死,dsh plugin add检测到插件把核心包声明为非 peer 依赖时给出警告。
在官方修复落地前,按本文的 find 清理即可恢复。这条排查逻辑也说明插件来源可信很重要——安装目录中经过人工验证的插件会少很多这类打包问题。如果你不想手动跟依赖树打交道,DeepSeek Harness 桌面端内置的 DSH Plugin Hub(dsh-plugin.org)集中管理已装插件,装了什么一目了然,还能一键卸载清理。
注意事项
- 报错发生在「有第二份核心包」时,先跑 find 再怀疑模型或框架,别在错误方向上空耗。
- 清理后必须开新会话:旧的失败会话已遗留 tool_calls 无结果,模型提供方会持续拒绝。
- 有多个 profile 时逐个检查,任何一个 profile 的残留都会让该 profile 工具调用全挂。
- 同类插件报错还汇总在《DeepSeek Harness 插件报错合集:DSH plugin 不加载、Web UI 异常与会话缓存修复》里。
常见问题
profile 的 node_modules 里出现了第二份 @deepseek-ai/dsh-tools。工具调度器用一个模块内 Symbol() 做键,两份副本的键不相等,agent loop 从另一份构造的服务里查调度器返回 undefined(来源)。
最常见是插件把 @deepseek-ai/* 声明成直接依赖(正确做法是放 peerDependencies);其次是打包 tarball 装进 profile 时带出传递副本;还有一种是早期安装遗留的 node_modules——即使 profile 已不引用它,残留的副本照样破坏工具调用。
用 find 检查 profile 的 node_modules:find <profile>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-agent' -print 应无输出;把 dsh-agent 换成 dsh-tools 再查一遍。有输出就是装了两份,mv 到备份目录后重启 dsh web 即可恢复。
不能用。失败的 turn 会遗留 tool_calls 却没有对应结果,下一轮请求会被模型提供方以 "An assistant message with 'tool_calls' must be followed by tool messages" 拒绝,会话只能放弃、开新会话。
清理重复副本即可恢复:删除或移走 profile 下残留的 node_modules/@deepseek-ai 副本(如 mv ~/.dsh/profiles/<name>/node_modules/@deepseek-ai /tmp/dsh-backup/),重启 dsh(Ctrl+C 后重新 dsh web)后新会话工具调用恢复正常;已损坏的旧会话放弃。
来源
- deepseek-harness Discussion #4640:profile 内重复的 @deepseek-ai/dsh-tools 静默破坏所有工具调用· deepseek-ai(GitHub Discussions)
- deepseek-harness packages/core/tools(dsh-tools,TOOL_RUNTIME_SCHEDULER 定义处)· deepseek-ai