在 DeepSeek Harness(下文直接称 dsh)的交流群里,被问得最多的问题根本不是“这个插件怎么用”,而是“插件装上之后完全不生效”。命令补全没出现、工具栏按钮不露面、对话行为一点没变,怎么看都像装了寂寞版本。这时候我一般懒得听对方描述完排查过程,直接回一句:先跑dsh --dump-config,把输出贴出来。这一句话能挡掉一大半的无效排查,因为多数人遇到的插件不生效,根子不在插件本身,而是 Harness 的加载链路压根没把插件算进去。
这篇文章就把这套判断逻辑完整写出来。适合两类人看:正被插件不生效折磨、已经卸载重装过三轮的,以及刚接触 dsh、想搞明白它插件机制到底怎么运转的。我会从--dump-config的输出解读讲起,然后逐条拆六类最常见原因,每类都给到可复现的排查步骤和验证手段。
1. 插件“装上没生效”,卡住的往往是加载链路
很多人的第一反应是插件坏了,于是删掉重装、换个版本、重启电脑,折腾一圈回来还是老样子。这个思路一开始就偏了。dsh 的插件体系在运行时分了三层:安装层、配置层、加载层。你从插件市场看到“安装成功”,只代表安装层完成了,文件被放到了某个目录;配置层决定这个插件是否被允许启用;最终真正把它拉起来的是加载层,由 Harness 内核在启动时扫描并注册。
“不生效”绝大多数发生在配置层和加载层的交接地带。典型场景是:插件已经被装到了~/.dsh/extensions/下面,但 Harness 启动时只扫描plugin.paths里列出的目录,而默认路径是~/.dsh/plugins和项目下的.dsh/plugins。于是文件确实存在,加载器却完全不知道有这个插件,自然什么都不发生。这种情况重装十次也不会有变化。
还有一类更隐蔽:你编辑的配置文件里写得明明白白,某个插件enabled = true,但同一份配置里存在多个作用域——系统级、用户级、项目级,还有环境变量覆盖。高优先级的作用域如果定义了同一个键,且值不同,低优先级的写入就会被打回。你明明改了配置,运行时读到的却是另一层的旧值。这种“配置改了但等于没改”的问题,靠肉眼盯文件看不出结果,必须看合并后的最终配置。
所以官方调试流程的第一步永远是--dump-config,而不是--version、不是--help、更不是把插件目录翻个底朝天。它输出的不是“你写了什么”,而是“运行时最终认为该加载什么”。这两个信息之间的差距,就是你要排查的对象。理解了这一层,后面所有操作才有意义。
2. 看懂 --dump-config:运行时的“最终裁决”长什么样
先明确命令名。不同发行版的入口多少有点差异,有的叫dsh,有的叫deepseek-harness,老版本还有人用harness,但诊断参数基本统一。执行:
dsh --dump-config推荐在当前项目根目录执行。如果你在其他目录跑,最好先cd到目标项目,或者用全局参数指定工作区,否则看到的作用域可能不是你以为的那个。
输出格式通常是 HJSON 或 JSON,核心结构类似下面这段:
{ version: 0.6.2 profile: default plugin: { enabled: true paths: [ "~/.dsh/plugins" "./.dsh/plugins" ] loaded: [ "harness-web-search@1.2.0" "harness-markdown-viewer@0.4.1" ] skipped: [ "harness-csv-tools@0.2.0" reason: "requires dsh >= 0.7.0" ] } }看这个输出,你只需要盯三个字段。
第一是plugin.paths,它是加载器实际扫描的目录列表。第二是plugin.loaded,这是本次启动真正挂载成功的插件清单,带版本号。第三是plugin.skipped,部分版本会输出跳过加载的插件,并给出原因,这是排查兼容性问题时的金矿。
如果paths里根本没有你插件所在的目录,那问题就在安装位置;如果paths正确但loaded里没有目标插件,那问题在配置开关或扫描逻辑;如果loaded里确实有,但功能不生效,那问题转向运行时执行层,就要换一套排查方法。
这里特别要提醒一个误区:--dump-config显示的是多层配置合并后的有效值,不是某个配置文件的原文。dsh 的配置作用域从上到下大概是这样:
| 作用域 | 典型位置 | 优先级 |
|---|---|---|
| 系统级 | /etc/dsh/config.hjson | 低 |
| 用户级 | ~/.dsh/config.hjson | 中 |
| 项目级 | ./.dshconfig | 高 |
| 环境变量 | DSH_PLUGIN_ENABLED等 | 最高 |
排查时常见的撞鬼现场是:项目里放了一个.dshconfig,里面把某个插件enabled = false,你却在用户级配置里把它设成true。按优先级,项目级赢了,插件不载入,但你看用户级文件怎么都对。--dump-config直接把最终值打出来,这个撞鬼现场一秒现形。
还有个小技巧:输出的顶层字段里一般会有profile和configSources,能列出本次启动实际读取了哪些配置文件、哪些被忽略。如果你改了配置但 dump 结果毫无变化,先看configSources里有没有包含这个文件,如果没有,说明你改的文件压根不在读取列表里,路径写错了。
3. 六类原因逐条排查:安装路径、配置层级、同名叠加
3.1 原因一:安装目录根本不在 plugin.paths 扫描范围内
这是我的实践中出现频率最高的原因,没有之一。dsh 插件市场的安装器一般会把插件放好,但只要你手工安装过插件,或者从 GitHub 直接git clone了仓库,目录位置就不可控了。
判断方式很简单:看 dump 输出里的plugin.paths,然后逐个对比你放插件的实际路径。比如你clone到~/code/harness-plugins/foo,默认路径里根本没有它,加载器不会跨目录去找,除非你在配置里显式加路径。
解决办法是在配置的plugin.paths里追加目录:
plugin: { paths: [ "~/.dsh/plugins" "./.dsh/plugins" "~/code/harness-plugins" ] }改完记得重新跑dsh --dump-config,看到新路径出现在输出里再继续验证插件。这里的经验是:不要为了省事把paths设成/或者家目录根路径。dsh 会递归扫描目录,范围太大会显著拖慢启动时间,还可能误加载一堆没打算启用的插件,到时候互相抢注册入口,排查更痛苦。
3.2 原因二:配置键写错层级,插件被静默忽略
dsh 的配置历史上经历过一次 schema 调整,网上大量教程和老博文还在用旧写法。旧版插件的启停是用plugins: { "harness-xxx": true }这种字段,新版调整成plugin.enabled加内置列表。兼容层会静默忽略旧字段,因为遇到未知字段直接报错会让所有老配置全部崩溃,他们选择的是容忍未知键。
所以很多人的真实情况是:配置里写了一大段plugins: { foo: true },dump 结果里完全没有这段的影子,既不报错也不生效。看配置文本怎么看都对,但运行时压根不理你。
排查时先翻 dump 输出里的完整字段名,以它为准。以新版为例,正确的启停方式通常是:
plugin: { enabled: true disabled: [ "harness-legacy-tools" ] }或者某些版本支持显式启用某一个:
plugin: { enabled: true load: [ "harness-web-search" ] }碰到这类问题,我的建议是把教程当辅助,把--dump-config输出的 schema 键名当唯一标准。配置改完之后,再从 dump 里找plugin段,看看你写的内容有没有真的进去,这一步能筛掉九成以上手滑写错层级的场景。
3.3 原因三:同名插件叠加,旧版本抢占了注册入口
这个原因比较隐蔽,踩到的人不多,但一旦踩上会非常困惑。现象是:你明明只启用了一个插件,但行为像是两个版本在打架,功能时而正常时而异常。
dsh 加载器按plugin.paths里的顺序扫描目录,同名插件先扫到先注册,后续重复 id 默认跳过。如果插件的 id 和目录名不完全一致,比如插件harness-web-search实际安装在v2.0.0-beta目录下,而全局旧版本放在先扫描的路径里,新版本就不会被注册。
排查时看 dump 的loaded列表,带版本号那部分就是最终胜出的版本。如果发现加载的版本比你预期旧,处理路径有两种:一是把包含新版本的目录在paths里提前,二是卸载掉旧版本,只保留新版本。
我的建议是后者,不要依赖路径顺序来“打架获胜”。路径顺序依赖会导致项目换一台机器部署时行为不一致,纯属给自己埋雷。插件市场安装应该走市场自带的管理器,重装前先卸载旧版本;手工目录复制的隐患就在这,目录里残留的旧版本文件会让市场管理器误判状态。
4. 六类原因逐条排查:版本兼容、缓存过期、作用域限制
4.1 原因四:Harness 内核与插件 API 版本不兼容
插件不生效时,很多人忽略版本兼容问题,因为现象看起来像完全没加载。dsh 插件系统对 API 有语义化版本约束,插件 manifest 里会声明它要求的内核版本范围,比如requires: "dsh >= 0.6.0"。不满足时,加载器会直接把插件放进skipped列表,并写明原因,而不是报个红错让你崩溃。
--dump-config输出里的version字段就是当前 Harness 内核版本,skipped段落里能看到被拒绝的插件和原因。比如我在上文示例里写的requires dsh >= 0.7.0,那你当前 0.6.2 的内核就是带不动它。
遇到版本不兼容,别急着换插件,先想清楚你到底需要什么。如果你需要的是插件的新功能,就升级 Harness 内核到插件支持的版本;如果你需要的是内核稳定,那就用旧版插件,或者找替代。这是一个取舍问题。
匹配度验证有个简单方法:把--dump-config输出里的version和插件目录下manifest.json的requires字段做一次显式比对。不用猜,直接写进脚本里做断言,CI 里每次部署前跑一遍也能提前拦下很多问题。
4.2 原因五:缓存索引过期,配置改对了运行时还是旧值
这类情况最让人窝火:配置改得全对,dump 输出也对,但实际执行时插件行为还是旧的。
dsh 在启动时会建插件索引和字节码缓存,目的和大多数框架一样,加速启动。代价就是,当插件目录文件变更、配置时间戳异常时,缓存可能失效判断出现偏差。尤其是你把配置文件放在云同步盘里,文件更新后时间戳被同步逻辑改成了过去时间,缓存系统以为文件没变,就继续用旧索引。
处理方式直接粗暴:清缓存。缓存目录通常在~/.cache/dsh或~/.dsh/cache,执行:
dsh clean-cache或者手动把缓存目录安全地删一遍,注意不是删配置,是删缓存。然后重新启动。
清理完缓存之后,还有一个市场索引层面的问题:插件市场源里的版本信息有本地缓存,新插件发布后你在市场里搜不到,或者搜到了但安装的是过期版本。这种情况运行插件市场刷新命令,比如dsh plugin update或者dsh plugins refresh,把远端索引拉回来。
我个人的习惯是:每次改配置文件、增删插件之后,强制走一遍“清缓存 → dump → 跑功能验证”三部曲。绕过缓存问题往往能让接下来的排查省掉大量时间。
4.3 原因六:作用域配置把你的插件挡在了当前项目之外
最后这类的现象很典型:同一个插件,在项目 A 里正常工作,切到项目 B 就完全消失。你在loaded里也找不到它。这不是插件坏了,而是作用域配置生效了。
dsh 支持按项目目录和 profile 匹配来决定加载哪些插件,这是为了避免大型工作区里插件互相干扰。配置大致长这样:
plugin: { rules: [ { match: "projects/**" load: ["harness-web-search"] } { match: "docs/**" load: ["harness-markdown-viewer"] } ] }如果你的项目路径不满足match条件,插件就不加载。排查时先看两个东西:当前运行目录和 profile 名称。--dump-config输出顶部一般有profile字段,如果你当前跑在defaultprofile 下,但插件配置在workprofile 下,自然不生效。
处理办法有三种:切换到正确的 profile(比如dsh --profile work)、把插件规则调整到匹配当前项目目录、或者直接用不限定匹配条件的全局启停。从实际运维角度看,除非有强烈的隔离需求,否则我建议插件规则尽量简单,用全局配置统一管控,出问题时排查成本低很多。
5. 问题树复盘:三分钟定位“不生效”的固定套路
当你熟练用--dump-config对号入座之后,可以把整套流程收敛成一张问题树,排查起来比我上面一步步读要快得多。
第一个分支:插件有没有被加载?看 dump 的loaded列表。没加载,进入第二个分支。加载了但功能不生效,跳到第六章的最小复现方法。
第二个分支:加载器有没有扫描到插件目录?看plugin.paths是否包含插件所在位置。不包含,这是路径问题;包含了,进入第三个分支。
第三个分支:插件有没有被杀掉?看skipped列表。有插件且写明原因,直接按原因处理。如果skipped里没有,再看配置文件里enabled相关的最终值。这里还要注意作用域覆盖,用--dump-config的最终值对照。
第四个分支:配置最终值也对,插件也在 loaded 里,但功能还是不对。走到这里基本就是运行时报错被吞、插件之间冲突、或者外部依赖缺失的问题。这时需要开 debug 日志:
dsh --loglevel debug run把输出的日志从头扫到尾,重点找插件名相关的 WARN 和 ERROR。插件注册命令时失败、加载 hook 时抛异常、连接本地服务失败,都会在这里留下痕迹。
这套问题树我建议你整理成自己的小抄,尤其是团队里有多个人用 dsh 的时候,让同事照着走一遍,比反复远程“帮看一下”效率高太多。
至于三条铁律,是我踩了无数次坑之后的总结:
一、先 dump 再动手。拿到别人的问题,不要上来让他重装插件,先让他跑dsh --dump-config。 二、改配置后不要凭界面判断。命令行直接验证功能,界面可能有缓存,命令行更能反映运行时真实状态。 三、不要靠猜。日志和 dump 输出已经给出了几乎所有信息,猜只会让你在错误方向上反复横跳。
6. 当 --dump-config 也正常时,怎么继续往下查
前面五类问题,--dump-config都能给你明确指引。但还有一批更刁钻的情况:loaded里有插件,路径正确,版本匹配,配置项全对,可功能就是不出来。这时候问题已经不在加载链路,而在运行链路。
我的做法是二分法最小复现。先把plugin.paths精简到只剩目标插件,其他插件目录全部移出扫描范围,保证没有同名冲突;然后把配置里其他钩子全部停掉,只保留目标插件的启停。这样如果功能恢复,说明是插件之间的运行时冲突;如果问题依旧,说明目标插件本身的本体或依赖有问题。
举个例子。之前有位用户报告harness-markdown-viewer插件不生效,--dump-config一切正常,loaded里有,版本号对得上。用二分法把其他插件全部停掉后,问题还是存在。后来开了 debug 日志,发现插件启动时要注册一个 webview 面板,注册函数依赖一个相对路径的配置文件,而用户是在系统临时目录直接打开.md文件测试的,连项目根目录都找不到,插件内部初始化直接提前 return。这不是加载问题,是插件对运行上下文的隐式依赖。换到正常项目目录里测试,一切正常。
这个案例说明,插件“不生效”可能只是你觉得它应该在任何场景下生效,但插件本身对运行环境有预期。所以排查到运行链路时,先检查三件事:插件是否有外部服务需要先启动(比如本地模型服务、文件监听)、插件是否依赖项目目录的结构、插件是否在输出日志里留下初始化失败痕迹但被上层忽略。
还有一类是首次触发懒加载的情况。部分插件不会在启动时立即执行所有初始化,而是等第一次调用命令、第一次打开文件、第一次触发鼠标右键菜单时才挂载。你如果刚改完配置就急着测试,可能时机没到。这种不算 bug,重启会话或触发一次相关事件后自然恢复。
最后一招,在确认是最新版本且问题复现稳定的前提下,把最小复现包提到项目的 issue 区。附上三项内容:完整的--dump-config输出、--loglevel debug的启动日志、一条可复现的测试路径。这三样东西给出来,维护者定位问题的时间会大幅缩短,你拿到修复版本的速度也会更快。
总结成一句话:--dump-config是把问题分层的好工具,先把“加载链路”这半边彻底确认完,再往“运行链路”里钻,你就能少走一大半弯路。