前面几节的 Agents、Skills、Rules,本质上都是提示词:写给模型看,模型可能听,也可能不听。
ECC 真正「硬」的部分在 Hooks。如果说前面几节是 ECC 的知识库,Hooks 就是它的运行时:不经过模型判断,到点就执行。
这一节是本章的重点。
一、全景:8 个事件,27 条注册

hooks/hooks.json 一共 285 行,用到了 8 个事件:
| 事件 | 条数 | 主要干什么 |
|---|---|---|
SessionStart |
2 | 加载上一次会话的摘要、学到的习惯(本章 第7节) |
UserPromptSubmit |
1 | 跑项目自定义的提示词规则 |
PreToolUse |
10 | 拦危险命令、保护配置文件、提醒压缩、先查后改 |
PostToolUse |
2 | 两个「分发器」,内部再分十几个小检查 |
PostToolUseFailure |
2 | MCP 健康检查、Skill 运行统计 |
PreCompact |
1 | 压缩前保存摘要(本章 第7节) |
Stop |
8 | 格式化 + 类型检查、console.log 检查、保存会话 |
SessionEnd |
1 | 收尾标记 |
之前学了 PreToolUse、PostToolUse、Stop 三个,ECC 用到了 8 个。
和记忆有关的 SessionStart、PreCompact 留到 第7节,这一节集中看其余的。
二、三层结构:定位 → 闸门 → 脚本

打开 hooks.json 你可能会吓一跳:每一条 command 都是一长串 node -e "...",几百个字符挤在一行。
拆开看,其实是三层:
hooks.json 里的一条命令
│
├─ ① 定位:找到 ECC 装在哪
│ (插件目录?~/.claude?插件缓存?旧名字 everything-claude-code?挨个找)
│
├─ ② 闸门:run-with-flags.js 判断这个 Hook 现在该不该跑
│
│ node scripts/hooks/run-with-flags.js pre:config-protection scripts/hooks/config-protection.js standard,strict
│ └─ Hook ID └─ 真正干活的脚本 └─ 哪些档位启用
│
└─ ③ 脚本:config-protection.js 做真正的检查
第 ① 层为什么写这么长?插件里的 Hook 可以用 CLAUDE_PLUGIN_ROOT 环境变量找到插件目录,但 ECC 还要兼容手动安装、旧版本名字等好几种情况,
只能挨个去找。全部用 Node 写而不用 bash,是为了 Windows 也能跑。
这一层是工程兼容,读的时候可以跳过。重点看 ② 和 ③,它们都在 scripts/hooks/ 目录下。
三、闸门:三个档位 + 单独开关

每个 Hook 都有一个固定的 ID,比如 pre:bash:tmux-reminder、post:edit:console-warn、stop:check-console-log。闸门根据三个环境变量决定放不放行:
export ECC_HOOKS_ENABLED=true # 总开关
export ECC_HOOK_PROFILE=standard # 档位:minimal | standard | strict
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,stop:desktop-notify" # 单独关掉某几个
核心逻辑在 scripts/lib/hook-flags.js,简化后就这几行:
function isHookEnabled(hookId, { profiles }) {
if (!areHooksEnabled()) return false; // 总开关关了
if (getDisabledHookIds().has(hookId)) return false; // 被单独点名关掉
const profile = getHookProfile(); // 当前档位,默认 standard
return parseProfiles(profiles).includes(profile); // 这个 Hook 属不属于当前档位
}
三个档位:
| 档位 | 开了什么 | 适合 |
|---|---|---|
minimal |
只留底线:拦 --no-verify、项目自定义规则、会话保存 |
想用 ECC,但不想被打扰 |
standard(默认) |
再加质量检查:配置保护、先查后改、批量格式化、console.log 检查、压缩提醒 |
大多数人 |
strict |
再加更多提醒:tmux 提醒、push 前提醒、提交前质量检查 | 要求最严的项目 |
之前讲的插件 userConfig,就是这里的另一个入口:环境变量没设,闸门就去读你在插件选项里填的值。
为什么要这么设计?「为了 Hook 而 Hook,挂得越多,Agent 越慢。」
ECC 有几十个 Hook,不可能每个人都要全部。
给每个 Hook 一个 ID,给整体分几个档位,用户就能按需关掉,而不用去改 hooks.json。
小技巧:Hook 的 ID 和说明并没有写在
hooks.json里,而是放在旁边的hooks/hooks.metadata.json。原因是 Claude Code 会校验hooks.json,出现它不认识的字段就报错。ECC 用一份「旁注文件」存元数据,再用 CI 脚本检查两边是否对得上。校验器认什么,就只写什么。