ECC Hooks 系统深度解析:PreToolUse / PostToolUse / Stop 钩子机制、权限安全与自定义实战
2026/9/10 23:55:31 网站建设 项目流程

ECC Hooks 系统深度解析:PreToolUse / PostToolUse / Stop 钩子机制、权限安全与自定义实战

【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC

Hooks 是 ECC(Everything Claude Code)中基于事件驱动的自动化机制,它们挂载在 Agent 每次工具调用的前后与会话生命周期边界上,用于强制执行代码质量、提前拦截错误并自动化重复性检查。本文以docs/ja-JP/rules/common/hooks.md的 Hook 架构规范为核心,结合仓库中的hooks/hooks.json配置图、hooks/README.md安装指南与scripts/hooks/下的真实实现,系统讲解 Hook 类型、自动接受权限的安全边界、TodoWrite 最佳实践,以及如何编写可跨平台运行的自定义 Hook。读完本文,你将掌握在 Claude Code / Codex / Opencode 等 harness 上部署、调优和扩展 ECC Hook 体系的完整方法。

Hooks 系统的工作流程

Hooks 是事件驱动的自动化,在 Claude Code 工具执行之前或之后触发。ECC 的核心执行链路可以概括为:

User request → Claude picks a tool → PreToolUse hook runs → Tool executes → PostToolUse hook runs
  • PreToolUsehooks 在工具执行前运行,可以阻止(exit code 2)或警告(stderr 不阻止);
  • PostToolUsehooks 在工具完成后运行,可以分析输出但不能阻止
  • Stophooks 在每次 Claude 响应后运行;
  • SessionStart / SessionEndhooks 在会话生命周期边界运行;
  • PreCompacthooks 在上下文压缩前运行,适合保存状态。

Hook 类型详解

PreToolUse:工具执行前(验证、参数修改)

PreToolUse 是唯一可以真正"阻止"工具调用的 Hook 类型,适合做验证与参数修改。ECC 内置了多个 PreToolUse 钩子,例如 pre-bash-dev-server-block.js 会在非 tmux 环境下阻止npm run dev等开发服务器命令(退出码 2),以保证日志可访问性。从源码可以看到,它会展开$(...)命令替换与(...)子 shell 分组递归收集所有命令行分段,防止$(npm run dev)这类包装命令绕过检查。

ECC 的 PreToolUse 钩子通常经过 run-with-flags.js 这个门控执行器运行——只有当当前 Hook Profile 允许该钩子时才真正执行脚本,否则直接透传 stdin 并以退出码 0 结束(fail-open,不干预工具调用)。该执行器还实现了路径穿越防护(拒绝指向插件根目录之外的脚本)以及大输入截断处理(超过 1MB 时抑制透传,避免截断的 JSON 被 harness 判定为 hook 失败)。

PostToolUse:工具执行后(自动格式化、检查)

PostToolUse 在工具完成后运行,典型用途包括自动格式化、质量门禁、构建分析与 PR 日志。ECC 在hooks/hooks.json中通过post:dispatcher:sync(同步)与post:dispatcher:async(后台异步)两个分发器钩子,在单进程内批量执行所有 PostToolUse 钩子,同时保留每个钩子各自的 profile 门控。

Stop:会话结束时(最终验证)

Stop 钩子在每次 Agent 响应结束后运行,适合做最终验证与状态持久化。典型例子是 check-console-log.js:它遍历本次修改过的 JS/TS 文件,检查是否残留console.log并给出警告;同时遵守always-exit-0约定,通过 stderr 输出警告而不阻断流程。源码中通过EXCLUDED_PATTERNS排除了测试文件、配置文件与scripts/目录(这些场景中console.log是合理的)。

ECC 的 Stop 钩子还承担了大量会话管理职责:Plan Canvas 待交付反馈投递(stop:plan-canvas-pending)、批量格式化与类型检查(stop:format-typecheck,对本次响应编辑过的所有 JS/TS 文件统一执行 Biome/Prettier 与tsc,避免每次 Edit 后都跑一遍)、会话状态持久化(stop:session-end)、模式提取(stop:evaluate-session,支撑持续学习)、成本追踪(stop:cost-tracker)与桌面通知(stop:desktop-notify)。

会话生命周期钩子

除三大核心类型外,ECC 还实现了完整的生命周期钩子:SessionStart(加载历史上下文并探测包管理器、恢复 Plan Canvas 浏览器评审)、PreCompact(上下文压缩前保存状态)、SessionEnd(生命周期标记与清理日志)、PostToolUseFailure(跟踪失败的 MCP 工具调用并标记不健康服务器)。

退出码语义与阻止/警告机制

Hook 通过退出码与 stdout/stderr 与 harness 通信:

退出码含义适用范围
0成功,继续执行所有 Hook
2阻止该工具调用仅 PreToolUse
其他非零错误(记录日志但不阻止)所有 Hook
  • 警告(warn):向 stderr 输出提示信息,工具调用照常进行;
  • 阻止(block):PreToolUse 钩子以退出码 2 退出,工具调用被拦截;
  • 透传约定:Hook 必须将原始 stdin 原样写回 stdout,否则 harness 可能误判为 Hook 失败。

一个需要特别注意的细节:非阻塞 PreToolUse 钩子的 stderr 只写入调试日志,不会进入模型上下文。若要向模型展示用户可见的建议,需要像 suggest-compact.js 那样,向 stdout 输出结构化 JSON 中的hookSpecificOutput.additionalContext字段。该钩子利用两个信号触发战略压缩建议:工具调用计数(默认每 50 次提醒一次)和会话 transcript 中真实的上下文 token 用量(按窗口比例缩放阈值,200k 窗口默认 160k 触发、1M 窗口 250k 触发)。

自动接受权限与安全边界

ECC 规则文档对自动接受权限(Auto-Accept Permissions)给出了明确的红线,必须谨慎使用:

  • 为受信任、定义明确的计划启用——当任务的每一步都是可预期、可审计的时候;
  • 为探索性工作禁用——当 Agent 的行为边界不清晰、需要逐步确认的时候;
  • 切勿使用dangerously-skip-permissions标志——这是绕过所有权限确认的"核选项",会抹掉 Agent 行为的全部审计痕迹;
  • 改为在~/.claude.json中配置allowedTools——以白名单方式精确放行特定工具,而不是一刀切跳过权限。

allowedTools的典型配置方式如下:

{ "permissions": { "allow": [ "Bash(npm run test)", "Read", "Edit" ] } }

这一安全立场与仓库中的安全指南一致。the-security-guide.md 明确指出:如果你无法看到 Agent 读取了什么、调用了哪个工具、尝试访问哪个网络目标,就无法对其设防;文中直接点名了在--dangerously-skip-permissions下运行多 Agent 循环并直接推送 main 分支的反面案例。ECC 的立场是:权限放行应当最小化、可审计、面向受信计划,而不是用跳过权限换取便利。

TodoWrite 最佳实践

TodoWrite 是长任务执行中的进度管理工具。ECC 规则要求使用 TodoWrite 工具来:

  • 跟踪多步骤任务的进度;
  • 验证对指令的理解(写出计划等于把理解显式化);
  • 实现实时指导(让用户/监督者随时看到 Agent 的当前步骤);
  • 展示细粒度的实现步骤。

更重要的是,Todo 列表本身是 Agent 对任务理解的"暴露面",一份糟糕的 Todo 列表会直接揭示:

  • 步骤顺序错误——说明对依赖关系理解有偏差;
  • 缺失的项目——说明遗漏了关键需求;
  • 额外不必要的项目——说明过度设计或误读了范围;
  • 粒度错误——过粗则无法跟踪,过细则淹没在噪音中;
  • 被误解的需求——Todo 与指令不一致时,应尽早纠正而非埋头执行。

实践要点:在任务开始前写 Todo,随执行进度实时勾选更新,在里程碑处对照 Todo 清单做偏差审查。

在 ECC 中安装与启用 Hooks

ECC 的 Hook 安装由官方安装器完成,仓库内hooks/hooks.json是面向插件/仓库形态的配置图,不建议手工将其粘贴到~/.claude/settings.json或直接复制到~/.claude/hooks/hooks.json——因为安装器会把 Hook 命令重写为针对你实际 Claude 根目录的路径。

bash ./install.sh --target claude --modules hooks-runtime --enable-hooks
pwsh -File .\install.ps1 --target claude --modules hooks-runtime --enable-hooks

安装后,解析好的 Hook 会写入~/.claude/hooks/hooks.json;在 Windows 上 Claude 配置根目录为%USERPROFILE%\.claude。内存持久化的生命周期定义位于 hooks/memory-persistence/ 目录,它是 SessionStart、PreCompact、观测、活动跟踪与 SessionEnd 行为的稳定契约。

运行时控制:环境变量与 Profile

ECC 推荐用环境变量在运行时控制 Hook 行为,而无需编辑 hooks.json

# 总开关。显式环境变量值优先于插件偏好设置。 export ECC_HOOKS_ENABLED=true # minimal | standard | strict(默认:standard) export ECC_HOOK_PROFILE=standard # 按钩子 ID 禁用指定钩子(逗号分隔) export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck" # 仅在安装/恢复期间禁用 GateGuard export ECC_GATEGUARD=off # 限制 SessionStart 附加上下文(默认 8000 字符) export ECC_SESSION_START_MAX_CHARS=4000 # 完全禁用 SessionStart 附加上下文 export ECC_SESSION_START_CONTEXT=off # 保留上下文/范围/循环警告,但抑制 API 速率成本估算 export ECC_CONTEXT_MONITOR_COST_WARNINGS=off

Windows PowerShell 下使用用户级环境变量:

[Environment]::SetEnvironmentVariable('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', 'User')

三个运行时 Hook Profile 的含义(源码见 scripts/lib/hook-flags.js,其中VALID_PROFILES只接受minimal | standard | strict三者):

  • minimal——只保留必要的生命周期与安全钩子;
  • standard——默认值,均衡的质量 + 安全检查;
  • strict——启用额外的提醒与更严格的护栏。

Claude 插件形态下,ecc setup --mode claude-plugin会安装或更新插件,并暴露与个人设置hooks_enabledhook_profile相同的选项。此外,ECC_GATEGUARD=offsetup-only值(通过ecc setup关闭本地 ECC Hook 工作),它不是运行时 Hook Profile。

仓库内置钩子清单

ECC 在 hooks/hooks.json 中维护了完整的可执行 Hook 图,以下按类型汇总:

PreToolUse Hooks

HookMatcher行为退出码
开发服务器阻止器Bash阻止在 tmux 外运行npm run dev等——确保日志可访问2(阻止)
tmux 提醒Bash对长时运行命令(npm test、cargo build、docker)建议使用 tmux0(警告)
git push 提醒Bash提醒在git push前审查变更0(警告)
提交前质量检查Bashgit commit前执行质量检查:lint 暂存文件、校验-m/--message提交信息格式、检测 console.log/debugger/密钥2(阻止严重)/ 0(警告)
文档文件警告Write对非标准.md/.txt文件给出警告(放行 README、CLAUDE、CONTRIBUTING、CHANGELOG、LICENSE、SKILL、docs/、skills/);跨平台路径处理0(警告)
战略压缩建议Edit\|Write在逻辑间隔(约每 50 次工具调用)建议手动/compact0(警告)

hooks.json中还注册了pre:observe(持续学习观测采集,异步)、pre:governance-capture(治理事件捕获,密钥/策略违规/审批请求,需ECC_GOVERNANCE_CAPTURE=1启用)、pre:config-protection(阻止修改 linter/formatter 配置文件,引导 Agent 修代码而非放宽配置)、pre:mcp-health-check(MCP 工具执行前健康检查,拦截不健康调用)与pre:edit-write:gateguard-fact-force(事实强制门禁:对每个文件的首次 Edit/Write 要求先完成调查再放行)。

PostToolUse Hooks

HookMatcher作用
PR 日志Bashgh pr create后记录 PR URL 与审查命令
构建分析Bash构建命令后的后台分析(异步、非阻塞)
质量门禁Edit\|Write\|MultiEdit编辑后运行快速质量检查
设计质量检查Edit\|Write\|MultiEdit前端编辑趋向通用模板 UI 时给出警告
Prettier 格式化Edit编辑后自动用 Prettier 格式化 JS/TS 文件
TypeScript 检查Edit编辑.ts/.tsx文件后运行tsc --noEmit
console.log 警告Edit警告已编辑文件中出现的console.log

Lifecycle Hooks

Hook事件作用
会话启动SessionStart加载历史上下文并探测包管理器
Plan Canvas 会话SessionStart呈现打开的 Plan Canvas 浏览器评审,让新会话恢复循环
压缩前保存PreCompact上下文压缩前保存状态
console.log 审计Stop每次响应后检查所有修改文件中的console.log
会话摘要Stop在可获取 transcript 路径时持久化会话状态
模式提取Stop评估会话中可提取的模式(持续学习)
成本追踪Stop输出轻量级运行成本遥测标记
桌面通知Stop发送 macOS 桌面通知并附带任务摘要
会话结束标记SessionEnd生命周期标记与清理日志

编写自定义 Hook

Hooks 本质上是 shell 命令:从 stdin 接收工具输入(JSON),并把结果输出到 stdout。跨平台(Windows / macOS / Linux)的关键是用 Node.js 实现 Hook 逻辑——ECC 的所有内置 Hook 均为此模式。

基本结构

// my-hook.js let data = ''; process.stdin.on('data', chunk => data += chunk); process.stdin.on('end', () => { const input = JSON.parse(data); // 访问工具信息 const toolName = input.tool_name; // "Edit", "Bash", "Write", 等 const toolInput = input.tool_input; // 工具特定参数 const toolOutput = input.tool_output; // 仅 PostToolUse 可用 // 警告(非阻塞):写入 stderr console.error('[Hook] 将展示给 Claude 的警告信息'); // 阻止(仅 PreToolUse):以退出码 2 退出 // process.exit(2); // 始终将原始数据输出到 stdout console.log(data); });

Hook 输入 Schema

interface HookInput { tool_name: string; // "Bash", "Edit", "Write", "Read" 等 tool_input: { command?: string; // Bash:正在运行的命令 file_path?: string; // Edit/Write/Read:目标文件 old_string?: string; // Edit:被替换的文本 new_string?: string; // Edit:替换后的文本 content?: string; // Write:文件内容 }; tool_output?: { // 仅 PostToolUse output?: string; // 命令/工具输出 }; }

异步 Hooks

对于不应阻塞主流程的 Hook(如后台分析),设置async: true并给出超时:

{ "type": "command", "command": "node my-slow-hook.js", "async": true, "timeout": 30 }

异步 Hook 在后台运行,无法阻止工具执行。ECC 内置的观测采集、会话持久化、成本追踪与桌面通知均采用异步模式。

常见配方

警告新增 TODO 注释:

{ "matcher": "Edit", "hooks": [{ "type": "command", "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const ns=i.tool_input?.new_string||'';if(/TODO|FIXME|HACK/.test(ns)){console.error('[Hook] New TODO/FIXME added - consider creating an issue')}console.log(d)})\"" }], "description": "Warn when adding TODO/FIXME comments" }

阻止创建超大文件:

{ "matcher": "Write", "hooks": [{ "type": "command", "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller, focused modules');process.exit(2)}console.log(d)})\"" }], "description": "Block creation of files larger than 800 lines" }

用 ruff 自动格式化 Python 文件:

{ "matcher": "Edit", "hooks": [{ "type": "command", "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.py$/.test(p)){const{execFileSync}=require('child_process');try{execFileSync('ruff',['format',p],{stdio:'pipe'})}catch(e){}}console.log(d)})\"" }], "description": "Auto-format Python files with ruff after edits" }

要求新源码文件附带测试文件:

{ "matcher": "Write", "hooks": [{ "type": "command", "command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file found for: '+p);console.error('[Hook] Expected: '+testPath);console.error('[Hook] Consider writing tests first (/tdd)')}}console.log(d)})\"" }], "description": "Remind to create tests when adding new source files" }

禁用或覆盖 Hook

要禁用某个 Hook,在hooks.json中移除或注释对应条目;以插件方式安装时,可在~/.claude/settings.json中覆盖:

{ "hooks": { "PreToolUse": [ { "matcher": "Write", "hooks": [], "description": "Override: allow all .md file creation" } ] } }

源码与测试验证

Hook 体系的实现集中在 scripts/hooks/ 目录(50 余个脚本),核心支撑包括:

  • run-with-flags.js——profile 门控执行器,同时处理截断保护、路径穿越防护与require()直载优化(导出了run()的钩子无需再 spawn 子进程,可节省约 50–100ms/钩子);
  • scripts/lib/hook-flags.js——环境变量解析与 Profile 合法性校验;
  • scripts/lib/utils.js——跨平台工具函数(Git 修改文件探测、stdin JSON 读取、日志与输出);
  • pre-bash-dispatcher.js 与 posttooluse-dispatcher.js——Bash 前置与 PostToolUse 的分发器;
  • session-start-bootstrap.js——会话启动引导。

测试方面,tests/hooks/ 下有覆盖上述机制的完整测试,例如hook-flags.test.js(环境变量与 Profile 解析)、pre-bash-reminders.test.js(tmux / push 提醒)、posttooluse-dispatcher.test.js(分发器行为)、doc-file-warning.test.jsmcp-health-check.test.jscontinuous-learning-observe-runner.test.js等,可作为理解钩子契约与编写新 Hook 的参考范例。

小结

ECC 的 Hooks 系统将"工具调用前验证、工具调用后检查、会话生命周期管理"三条自动化主线统一在hooks/hooks.json的 Hook 图之下,并借助 profile 门控与运行环境变量实现细粒度启停,无需改动配置即可在minimal/standard/strict之间切换。对使用者而言,牢记三条核心纪律即可:PreToolUse 才能阻止、永远不要使用dangerously-skip-permissions、用 TodoWrite 暴露对任务的理解。在此基础上,参考scripts/hooks/下的实现与tests/hooks/下的测试,你就能写出安全、跨平台、可复用的自定义 Hook,把 ECC 的自动化护栏延伸到自己的工程实践中。

【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询