开源 Agent 套件 ECC 装完不干活:自检脚本、钩子失灵与排查顺序

2026-07-29

本文基于 ECC 仓库 commit 591ab5c(2026-07-29)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/affaan-m/ECC 最新代码与文档为准。

装完不干活,绝大多数时候不是套件本身坏了,而是「安装记录里的那份文件清单」和「你机器上现在真实存在的文件」对不上。 认下这个判断,排查顺序就自然出来了:先证明文件层是完整的,再证明钩子那一层确实被加载了,最后才去怀疑某个 agent 或某个技能本身有毛病。倒着查最费时间——你盯着一个不触发的钩子调半天,结果发现它压根没被写进 ~/.claude/hooks/hooks.json

ECC 是一套装在编码 Agent 之上的增强件,采用 MIT 许可证。它的体量决定了排障成本:agents 目录 67 个 agent、skills 目录 281 个技能、commands 目录 94 个命令,再加上一整张钩子图。东西一多,「到底是哪一层没生效」就成了主要开销。它自带一组自检命令来压缩这个开销,这篇讲的就是这组命令能告诉你什么、以及它明确告诉不了你什么。

站内已有的 Claude Code 安装失败排查 讲的是安装环节的通用路数,Agent 框架调试 讲的是框架层出问题时的通用方法论。本篇不重复那两套思路,只看一个具体项目把它们落成了哪些真实的脚本、哪些能直接搜的报错码、哪几个坑是文档里白纸黑字记下来的。

一、先把「不干活」拆成三类

这三类的排查动作完全不同,混在一起查必然绕远路。

第一类是文件层没到位。 表现是「Agent not loaded」「Unknown agent」这种找不到东西的报错,或者插件装完功能压根不出现。仓库根目录的 TROUBLESHOOTING.md 把这类归到 Agent Harness Failures 和 Installation & Setup 两节,建议的第一动作不是重装,而是先跑 ecc list-installedecc doctorecc repair,只有在 doctor 和 repair 都恢复不了缺失文件时才考虑重装。这个顺序值得照抄:重装会把你之前的本地改动一起冲掉,而 doctor 只读不写。

第二类是钩子层没加载或加载了但被判成失败。 表现是你明明配了 PreToolUse 钩子,命令照样过去了;或者钩子实际跑成功了,转录里却挂着 Hook Error 标签。前者多半是注册问题或脚本没有执行权限,后者是 docs/hook-bug-workarounds.md 里单独列出来的一类上游行为,跟 ECC 本身没关系。

第三类是会话层的压力问题。 表现是反复的 529 Overloaded、比预期更早的压缩、MCP 连接器在界面上显示已认证但调用就失败。这类问题查文件、查钩子注册都查不出来,因为文件和注册都是好的。

判错类别的代价很实在:第三类问题按第一类去查,你会反复重装插件,问题一次都不会好。上下文这条线上的表现和归因,可以对照 上下文污染 那篇看,症状很容易和「套件坏了」混淆。

二、ecc doctor 到底在查什么

scripts/doctor.js 这个文件本身很薄,真正的判定逻辑在 scripts/lib/install-lifecycle.jsbuildDoctorReportanalyzeRecord 里。理解它的模型对排障很有用:ECC 安装时会写一份安装态文件,把「我往你机器上放了哪些文件、从哪个源文件放的」记下来;doctor 干的事就是拿这份记录去和磁盘现状对账。

对账会得出一批带严重等级的问题项,每项有一个稳定的 code。这些 code 可以直接拿去搜代码、搜 issue,比自然语言报错好用得多:

  • missing-managed-files:记录里有、磁盘上没有的托管文件,属于 error。这是「装完不干活」最常见的真因。
  • drifted-managed-files:文件在,但内容和源仓库不一致,属于 warning。你自己手改过、或者半途升级过,都会落到这里。
  • missing-source-files:安装态引用的源文件在当前仓库里找不到了,error。常见于你切换了分支或者仓库位置变了。
  • missing-target-root:记录的目标根目录压根不存在,error。
  • target-root-mismatch / install-state-path-mismatch:记录的路径和当前算出来的路径不一样,warning。换了 home 目录、换了机器、跨平台同步过配置,都会撞上。
  • manifest-version-mismatch / repo-version-mismatch:清单版本或仓库版本和记录里的对不上,warning。这是升级后行为突变的常见解释。
  • resolution-drift:按当前清单重新解析出来的模块选择,和当初装的时候记的不一样,warning。
  • unsafe-managed-destination / unsafe-repair-source:托管操作指向了不安全的目标或源,error。代码里对这类情况的处理是拒绝执行而不是继续修,其中一种触发原因是目标末端是符号链接。
  • invalid-install-state:安装态文件本身读不出来或不合法,error。它的约束定义写在 schemas/install-state.schema.json,但运行时真正执行校验的不是那份 schema,而是 scripts/lib/install-state.js 里手写的一个校验器。文件注释把理由讲得很直白:安装闭环不允许引入任何非内置依赖,为的是让「审过的那份字节就是装上机器的那份字节」。这个取舍对排障的影响是——报错文案来自手写校验器,你拿着 schema 里的字段名去搜不一定能对上,反过来搜 install-state.js 更快。

输出形式上,人读模式会逐条打印适配器 id、状态、安装态路径和问题列表,末尾给一行 checked / ok / warnings / errors 的汇总;加 --json 则打印完整结构。有一点要留意:只要存在 error 或 warning,进程退出码就是 1。也就是说把 ecc doctor 塞进 CI 或者 shell 脚本里做判断时,一个纯 warning 的漂移也会让你的脚本红掉,这个尺度得自己收。

状态字段只有四种取值:okwarningerrormissing。前三种由问题项里最严重的那一条决定,missing 是另一回事——它表示这条记录根本没有可用的安装态。

查完 doctor,修复动作是 ecc repair。这个命令重建的是安装态里记录过的托管文件,--dry-run 可以先看它打算动什么。两个命令都支持 --target 收窄范围,比如 ecc doctor --target opencode。仓库当前支持的安装目标是一个明确的枚举:claudeclaude-projectcursorantigravitycodexgeminiopencodecodebuddyjoycodeqwenzedhermesopenclawkimi。传枚举以外的值会直接报未知参数。

排障时最费时间的往往不是分析,是「这个东西到底在哪」。下面这张表按你实际会碰到的顺序列出相关部件:

组成部分它负责什么仓库位置你什么时候会碰到它
安装态清单记录装了哪些托管文件、从哪个源来schemas/install-state.schema.json怀疑装的东西不全时
已装目标列表打印每个目标的根目录、安装时间、模块、源版本scripts/list-installed.js排查第一步,确认到底装到哪了
自检拿安装态和磁盘现状对账,输出带 code 的问题项scripts/doctor.js排查第二步
对账逻辑本体判定 missing / drifted / mismatch 各类情况scripts/lib/install-lifecycle.js想搞清某个 code 的确切含义时
修复重建安装态里记录过的托管文件,支持 --dry-runscripts/repair.jsdoctor 报 error 之后
目标适配器每个安装目标一个文件,决定它的根目录和安装态落点scripts/lib/install-targets/(如 claude-home.js路径 mismatch 类问题
钩子生命周期与退出码约定说明 PreToolUse/PostToolUse/Stop/SessionStart/PreCompact 各阶段行为hooks/README.md钩子写了不生效时
钩子开关按 profile 和 id 决定某个钩子启不启用scripts/lib/hook-flags.js想临时关掉某个钩子时
上游 bug 速查集中几条钩子/压缩/MCP 的恢复动作docs/hook-bug-workarounds.md钩子行为诡异但文件都正常时
全量排障面内存、harness、性能、常见错误信息TROUBLESHOOTING.mddocs/TROUBLESHOOTING.md前面几步都没定位到时

注意仓库里有两份 TROUBLESHOOTING:根目录那份是 ECC 自身的问题面,docs/ 下那份写的是「上游编码工具的 bug 会怎么波及 ECC 用户」。分工不同,别拿错。

三、钩子这一层的已知坑

钩子是这类套件最容易出事的部位,因为它跨了进程边界,出问题时反馈信号极弱。仓库里记了几个值得先记住的形态。

假的 Hook Error 标签。 钩子实际跑成功了,转录里仍显示报错。文档给的动作是:在 shell 钩子开头就把 stdin 消费掉(input=$(cat)),避免父进程看到一个没被读完的管道;简单的放行/拦截钩子把可读诊断信息发到 stderr、让 stdout 保持安静;子进程里不可操作的噪声 stderr 该重定向就重定向。退出码的约定要背下来:0 放行,2 拦截,其它非零一律被当成错误。写钩子时把「我想报个错」写成 exit 1,就会得到一个真的 Hook Error 而不是拦截效果。

改了钩子不热重载。 改完 settings.json 里的钩子配置,当前会话不会生效,必须重启会话。文档同时说明 ECC 没有内置 reload 命令,理由是那类做法依赖具体 shell 和平台、不够可靠。这条坑的代价是你可能会误判「我的修改没用」,然后去改一个本来就对的地方。

压缩比预期来得早。 文档记录的现象是:调低那个自动压缩比例的覆盖环境变量,在某些构建上反而让压缩提前发生而不是推后。给出的处理是把这个覆盖变量去掉,改在任务的自然边界上手动 /compact,并用 ECC 的 strategic-compact 指引替代硬压阈值。

MCP 连接器在压缩后失效。 界面上还显示已认证,工具调用却失败。恢复动作是把受影响的连接器关掉再打开;如果你的构建支持 PostCompact,可以挂一个提醒钩子。文档明确把这个定性为认证状态的恢复步骤,不是永久修复。

高压下反复 529 Overloaded 给出的几个方向都是降压:减少工具定义的压力、给日常工作调低思考预算、把子任务路由到更便宜的模型、按项目关掉用不上的 MCP 服务、在自然断点手动压缩。各家模型服务商的规则不同且会调整,具体以官方最新说明为准,这里只看机制——压力来源是工具定义数量、上下文量和并发,不是某个神秘开关。

还有一条形态很特别的坑值得单独提:在 Termux/Android 上,文档记录过 OpenCode 侧因为 plugins/lib/ 没拷完整,导致 changed-files 工具和 ecc-hooks 插件共同依赖的那个模块找不到。因为插件入口在会话启动时最先加载,早期版本会让整个会话直接崩掉,而不只是坏掉一个工具;新些的版本改成只打一次 [ECC] changed-files tracking disabled 警告然后静默降级。这两种表现差别巨大,但根因是同一个:安装没拷完。对应的动作还是 ecc doctor --target opencodeecc repair --target opencode

顺带一提,同一份文档里明确划了一条界:如果你看到某个第三方 slim 插件的模型前缀报错,那属于另一个项目的配置文件,ECC 不往那个目录写东西。这种「不是我」的声明在排障文档里是有价值的,它帮你砍掉一整个方向。关于钩子机制本身,可以配合 Claude Code hooks 那篇看基础约定。

四、缩小范围的推荐顺序

把上面几节合成一条可执行的路径:

  1. 先看装到哪了。 ecc list-installed,确认目标、根目录、安装时间、选中的模块、源版本。如果这一步就打印「没找到安装态」,后面全都不用查了。
  2. 再对账。 ecc doctor,先看汇总行的 errors 数。有 error 就优先看 missing-managed-filesmissing-source-files——前者是文件掉了,后者是源没了,处理方式不同。
  3. 修之前先空跑。 ecc repair --dry-run 看清它要动什么,再去掉 --dry-run
  4. 文件层干净之后再看钩子。 确认钩子确实写进了目标位置。手动安装时不要把仓库里的 hooks/hooks.json 直接粘进 ~/.claude/settings.json,那份是面向仓库和插件形态的,路径没有针对你的机器重写;正确做法是走安装器(例如 bash ./install.sh --target claude --modules hooks-runtime,Windows 上用对应的 install.ps1),由它把命令重写成你实际的根路径。
  5. 钩子在但不触发,先查权限和换行符。 根目录 TROUBLESHOOTING 里对应两条常见报错:EACCES: permission denied 走给脚本加执行位,spawn UNKNOWN 是 Windows 上的 CRLF 问题,用 dos2unix 转成 LF。MODULE_NOT_FOUND 则是插件目录里的依赖没装。
  6. 都正常还是不对,才怀疑功能本身。 到这一步再开调试日志、再去看某个具体 agent 或技能的行为。日志这块的取舍可以参考 可观察日志设计

这个顺序的价值在于每一步都在做二分:文件层过了就永久排除文件层,钩子注册过了就永久排除注册。跳步的代价是你会在同一个假设上来回打转。

五、边界与代价:它明确不管什么

这套自检有清晰的能力上限,认清楚才不会误判。

它只对账它自己记过的东西。 doctor 的整个判断建立在安装态记录之上。你手工往目标目录里塞的文件、你自己改的 settings.json 段落、别的插件写进去的内容,它既不知道也不负责。反过来说,drifted-managed-files 这个 warning 恰恰会在你合理地手改了托管文件时出现——这是设计取向问题:它优先保证「和源仓库一致」这件事可被检测,代价是把你的自定义也标成漂移。

上游工具的 bug 它修不了。 docs/ 下那份文档从第一句就声明这些是上游编码工具的行为而非 ECC 的缺陷,给的都是权宜动作,不承诺根治。假 Hook Error、压缩时机、连接器认证这三类,你能做的是绕,不是修。

模型侧的安全策略它也绕不过。 文档里专门写了一条:对自己的代码跑安全审查时,可能撞上模型层面的安全策略拦截,并明确说这不是 ECC 的拦截、任何 ECC 配置都改不了它。给出的方向是走官方的用途审核流程,以及在审核通过前把提示词写得偏向修复而非攻击、一次只审一个模块。这条声明的诚实度值得肯定,但你要接受的现实是:这条路上 ECC 帮不了你。

它会往你机器上写文件、挂钩子、接外部服务,这是实打实的代价。 观测记录会持续写到本地目录并可能长到很大,文档给的处理是归档压缩而不是直接删;钩子会在每次工具调用前后跑真实的子进程,跑得多了会体现在响应时间和 CPU 上。装这类套件本质上是拿一部分本地资源和一部分不可见的执行面,换自动化。判断值不值得之前,起码要知道观测文件写在哪、怎么临时停掉。文档给的临时停法很直白:往对应目录放一个 disabled 标记文件。

图形面板不属于最小可用集。 仓库自带的桌面面板依赖 Tkinter,很多 Python 发行版并不带;浏览器版面板只要 Node。更关键的一条限制是:这两个命令都必须在完整克隆的仓库里跑,插件形态的安装不带 package.json 里的脚本。拿插件目录去跑 npm run 只会白折腾。

六、上手与避坑清单

每条都写清为什么会踩,以及怎么避。

别拿重装当万能解。 会踩是因为重装看起来最快;代价是你在目标目录里的本地改动会一起没掉,而且如果真因是源文件缺失或路径 mismatch,重装并不能解决,你只是把同一个坑再挖一遍。避法是把 ecc doctor 当成动手前的强制步骤,看清 code 再决定。

别把仓库里的 hooks/hooks.json 直接粘进用户配置。 会踩是因为它看起来就是一份现成的钩子配置;实际它是面向仓库/插件形态的,里面的命令路径没有针对你的机器重写,粘过去的结果就是钩子静默不工作。避法是走安装器安装 hooks 相关模块,让它把路径解析好再落盘。

写钩子时别把「我要报错」写成 exit 1 会踩是因为 Unix 习惯里非零就是失败;但这里 2 才是拦截,其它非零会被当成钩子本身出错。避法是把三个退出码当成协议背下来:0 放行、2 拦截、其它是故障。

别在 shell 钩子里忽略 stdin。 会踩是因为很多钩子逻辑用不上输入内容,就懒得读;结果是父进程看到未消费的管道,给你挂一个假的错误标签。避法是第一行就 input=$(cat),用不用得上另说。

改完钩子记得重启会话再判断效果。 会踩是因为大多数配置文件都支持热加载,你会默认它也会;结果是你对着一个没生效的修改反复怀疑自己的逻辑。避法是把「重启会话」当成修改钩子的固定收尾动作。

关钩子优先用环境变量,别直接删配置。 会踩是因为删得快;代价是你之后想恢复要靠记忆。ECC 提供了运行时开关:ECC_HOOK_PROFILEminimalstandardstrict 三档之间切(默认 standard),ECC_DISABLED_HOOKS 用逗号分隔的 id 精确关掉某几个,ECC_GATEGUARD=off 在装机或恢复期单独关掉守卫。这些开关的解析逻辑就在 scripts/lib/hook-flags.js 里,传了非法 profile 值会静默退回默认档——这点要小心,拼错了不会报错,只会让你以为切了档其实没切。

跨机器同步 ~/.claude 之前想清楚。 会踩是因为把配置目录当成纯文本同步很自然;结果是安装态里记的根路径和新机器算出来的对不上,doctor 会报出路径 mismatch 类的 warning。避法是在新机器上重新跑一次安装或 repair,而不是指望目录拷贝。

别在 CI 里拿 ecc doctor 的退出码当二值判断。 会踩是因为退出码 0/1 看起来就是通过/失败;实际只要有 warning 就是 1,而 drifted-managed-files 这种 warning 在有自定义的环境里可能长期存在。避法是用 --json 拿结构化结果,自己按 summary.errorCount 判断。

多目标环境记得带 --target 会踩是因为不带参数时会把当前 home 和项目上下文里能发现的安装态全查一遍,输出很长,你容易在一堆无关目标里找错重点。避法是先 ecc list-installed 看清有哪些,再收窄。


把这一整套压成一个最小自检序列:ecc list-installed 确认装到哪 → ecc doctor 看 errors 数和 code → ecc repair --dry-run 看修复计划 → 确认钩子是走安装器落到目标位置的 → 检查脚本执行位和换行符 → 重启会话 → 还不对再开调试日志。

如果你想再往里挖一层,读文件的顺序建议是:先 scripts/doctor.js 看输出形状(很短,几分钟就能读完),再 scripts/lib/install-lifecycle.js 里的 analyzeRecord 看每个 code 的确切触发条件,最后 hooks/README.md 看钩子生命周期和退出码约定。这三份读完,绝大多数「装完不干活」你都能自己定位到具体哪一层,而不必靠猜。

本文属于 ECC 开源 Agent 套件专题(共 40 篇,从选择性安装一直拆到内部机制)。同一批还写了另一种取向的项目——不给你资产、只给你纪律的 superpowers 方法论专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。