mcpField
C
cc-hooks

cc-hooks

github.com/boshu2Agent 工作流45044 次安装今天更新
代码仓库

关于 cc-hooks

这是用于配置 Claude Code 生命周期钩子的技能,可通过 Shell 命令执行范围明确、可测试的规则。它包含默认策略分发器和可选防护,并要求成功时保持静默,避免干扰工具调用。

主要功能

  1. 01生命周期事件钩子
  2. 02工具调用前拦截与转发命令
  3. 03策略注册表与分发器
  4. 04可选的读取和技能编辑防护
  5. 05成功时静默并明确拦截

适用场景

  1. 01执行前拦截有风险的命令
  2. 02将已安装技能的编辑转回源文件
  3. 03限制超长文件读取

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/boshu2/agentops --skill cc-hooks

安装后在 Claude Code 中输入/cc-hooks确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

skill.spec.json
hooks/
codex-read-budget-guard.sh
installed-skill-edit-guard.sh
policy-dispatch.sh
read-budget-guard.sh
skill-first-coord-guard.sh
policies/
policies.json
references/
DCG-RCH.md
GUARDRAIL-VALUE-PROOF.md
HOOK-EVENTS.md
INSTALLED-SKILL-EDIT-GUARD.md
JSON-OUTPUT.md
PATTERNS.md
READ-BUDGET-GUARD.md
SKILL-FIRST-COORDINATION-GUARD.md
scripts/
install-hooks.sh
lint-policies.sh

详细介绍

Claude Code 钩子

在 Claude Code 生命周期的特定时间点触发的 Shell 命令。

钩子可以强制执行仅靠文字说明无法做到的事:模型可以想办法绕过一条指令,但无法绕过退出码 2——这正是每个钩子都必须范围明确、保持静默且可撤销的原因。

命名失败模式——正常路径话太多:退出码为 0 时输出 stdout 的钩子会破坏它所守护的工具调用;成功时保持静默是契约的一部分,而非风格偏好。

提示词

Add a PreToolUse hook to fleet-router/.claude/settings.json that blocks `git push --force` on the main branch. Keep it silent on exit 0, exit 2 with a message on block, and confirm it fires with a manual test invocation before committing the change.

满足以下条件即表示正常工作

  • 当钩子阻止 git push --force 时,脚本以退出码 2 退出并输出 stderr 消息;在允许的路径上则以退出码 0 退出,且不输出 stdout。
  • .claude/settings.json 为新钩子新增一个匹配器条目,与现有钩子列表并列,而不是替换它。
  • 在提交更改之前,针对新匹配器手动调用测试,转录记录中显示拦截触发,且能看到退出码 2。
  • 钩子只检查它所守护的 PreToolUse 调用,其他文件一律不动。

约束

  • 强制执行钩子(PreToolUse 策略分发器)默认启用:插件安装会自动接入 hooks/hooks.json;技能副本和检出版本通过一条命令接入(scripts/install-hooks.sh)。操作者可以按主机禁用(/plugin disable,或移除设置中的匹配器)。
  • 注入钩子(SessionStart/UserPromptSubmit 上下文填充)保持禁用状态——#511 的拆除验证表明,在 10.35M 个驻留 token 下,delta=0。切勿启用此类钩子;无钩子冷启动门禁仍会强制执行这一要求。
  • 保持正常路径静默,并且仅依据事件文档规定的退出码/JSON 契约进行拦截,因为多余的 stdout 可能会破坏工具调用。
  • 使用 stop_hook_active 限制 Stop 钩子,并缩小匹配器范围,以防止递归和拦截无关命令。

快速开始

添加到 ~/.claude/settings.json(用户级)或 .claude/settings.json(项目级):

{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"my-validator.sh"}]}]}}

钩子事件

事件触发时机能否拦截?常见用途
PreToolUse工具运行前是拦截/修改命令
PostToolUse工具成功后提供反馈自动格式化、lint
PermissionRequest权限对话框是自动批准/拒绝
UserPromptSubmit提交提示词时是添加上下文、验证
StopClaude 完成时是强制继续
SessionStart会话开始时否加载上下文、设置环境变量
Notification通知时否桌面提醒

完整架构:HOOK-EVENTS.md

匹配器

"Bash"              → exact match
"Edit|Write"        → regex OR
"mcp__.*__write"    → MCP tools
"*" or ""           → all tools

工具:Bash、Read、Write、Edit、Glob、Grep、Task、WebFetch、WebSearch

退出码

代码效果
0成功——从 stdout 解析 JSON
2拦截——将 stderr 传给 Claude
其他非拦截性错误

拦截工具

简单方式(退出码 2):

echo "Blocked: reason" >&2 && exit 2

JSON 方式(退出码 0):

{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Blocked"}}

决策:"allow"(自动批准)、"deny"(拦截)、"ask"(显示对话框)

修改输入

{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow",
  "updatedInput":{"command":"modified-command"}}}

实际应用:DCG + RCH

{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[
  {"type":"command","command":"dcg"},
  {"type":"command","command":"rch"}
]}]}}
  • DCG:拦截 git reset --hard、rm -rf、git push --force
  • RCH:将构建任务路由到远程工作器

详情:DCG-RCH.md

Skill-First Coordination Guard(可选启用)

可复制粘贴的 PreToolUse 配方,提醒 Agent 先加载协调技能,再手动编写 am/atm/ntm/tmux send-keys CLI。此配方不会自动安装;需针对每个宿主选择启用(不同于默认随附的策略分发器)。

钩子的上下文预算原则:钩子是最强大的强制机制(机械执行,无法通过推理绕过),但会污染上下文,因此应谨慎使用。钩子在正常情况下必须保持静默(退出码为 0,不输出 stdout/stderr),仅在确实违规时触发(理想情况下每个会话仅触发一次,并由哨兵控制);优先使用 PreToolUse 违规防护,而非 UserPromptSubmit/SessionStart 逐轮注入器;在退出码为 0 的 PreToolUse 路径上,绝不能输出多余的 stdout(它会被解析为 JSON 并导致工具调用失败)。通过退出码 2 + stderr 进行拦截。

此配方原样提供两个脚本、精确的仅匹配命令开头的匹配器(因此 br create --body "...am/atm/ntm..." 绝不会误触发)、双匹配器的可选启用 settings.json 片段,以及一项能验证所有触发和静默场景的 bats 测试。

配方:SKILL-FIRST-COORDINATION-GUARD.md

Installed-Skill-Edit Guard(可选启用)

一个 PreToolUse Edit|Write 防护机制,可将对已安装技能副本(*/.claude/skills/**、.codex、.gemini)的编辑引导回仓库中唯一可信的源文件 skills/<name>/。这是一个真正会造成错误的操作:编辑已安装或符号链接的副本没有合理用途(安装时会被覆盖,或者符号链接会指向工厂检出目录)。误报面为零:它只匹配 tool_input.file_path,因此正文中仅提及 claude/skills 的文档绝不会触发。可撤销 → 它会进行引导(退出码 2 + 单行重定向提示),而非硬性拦截。其他任何情况下均保持静默;每个会话仅触发一次。默认不启用 — 需使用可选启用安装器:

scripts/install-installed-skill-edit-guard.sh   # user scope; --project for project

配方:INSTALLED-SKILL-EDIT-GUARD.md

价值验证(为什么移除无钩子机制后,这项防护仍值得保留)

核心防护提供不受闸门影响的逐次触发遥测:每次触发时,它都会向 ${AGENTOPS_HOME:-~/.agents/ao}/guardrail-telemetry.jsonl 追加且仅追加一行 JSONL — {ts, session, token_class, path_sha256}(可通过 AGENTOPS_GUARDRAIL_TELEMETRY 覆盖)。路径经过 SHA-256 哈希处理,绝不记录原文(保护隐私);正常情况下不会写入任何内容;只有安装并触发防护后,传感器才会启用。预先注册的方法 — 指标 = 随时间推移,触发尝试率逐渐下降(这是重定向无法伪造的信号,不是循环定义的手动编写率)、最小 N、噪声下限,以及N 较小时得出无结论是可接受的结果 — 满足 ADR-0002 第 58 行(“证明其价值的测试或评估证据”),该标准的缺失曾导致 2.x 钩子被淘汰(#511)。

方法:GUARDRAIL-VALUE-PROOF.md

读取预算防护(选择启用)

一个 PreToolUse Read|Bash 防护,用于拒绝读取行数超出预算且未设上限的操作(AOP_READ_BUDGET_LINES,默认值为 350):没有 limit 的 Read,或有效行数超出预算的 cat/head/tail。Spotify 事件的教训是:CLAUDE.md 中的同一规则只是建议,因此被忽略;超出预算的读取还会在后续每一轮重新发送这些行。判断条件是查找(对确切参数执行 wc -l),因此它是独立防护,绝不是注册表策略。受 limit 限制的片段、行数不超过预算的文件、管道、重定向,或仅提及 cat 的引号文本,都会通过。保留字面引号或转义的路径;不支持的 Shell 语法和切换目录的命令链会放行。对于负数 head 行数,按 GNU head 实际保留的行数计算;在检查可执行文件身份后,Darwin 系统工具不支持的标志会放行。带正号的正数 head 行数按其数值进行限制。一旦字节进入上下文,就无法撤回 → 每次尝试都会拦截(退出码 2 + stderr):每个会话仅首次显示完整消息,说明两个正确做法(分片读取,或委派给插件的 agentops:bulk-reader 子 Agent/agentops:bulk-read 工作流),之后只显示一条简短提示。仅在运行时列出裸名称时,才将其用于独立定义或链接。通过 AOP_WAIVE=core.context:unbounded-read 豁免一次;哈希遥测会添加 tool、lines、budget,以及分发器的 mode/decision 对。

默认禁用 — 通过选择启用的安装程序启用:

scripts/install-read-budget-guard.sh   # user scope; --project for project

配置说明:READ-BUDGET-GUARD.md

策略分发引擎(默认启用)

准入控制层(史诗 age-4qw1):一个 PreToolUse 分发器 — hooks/policy-dispatch.sh — 评估以数据定义的策略注册表(policies/policies.json,契约为 schemas/hooks-manifest.v2.schema.json),取代 N 个手动接线的设置条目。这是工具调用层级上的隔离层:使用相同的词汇,但层级低于推送时的棘轮/闸门。

每项策略包括:dcg 风格的 ID(domain.object:token)、mode: deny | route | audit、匹配器(工具 + command/file_path 正则表达式)、指明正确工具的 route_message、理由,以及预先注册的 value_proof(ADR-0002 的生命周期承诺:没有累积证明 → 退役该策略)。

谓词约束,由架构强制执行(#511 的反面教训):只有 predicate_class: pure —— 针对命令或文件路径的语法错误标记 —— 才能 deny/route。查找/有状态谓词在通过审查的触发记录促成晋升前,只能以 audit 模式启用。scripts/lint-policies.sh 以机械方式强制执行此规则(仅依赖 jq;在 bats 和 CI 中运行)。

可接受的误报范围: 由于纯谓词会匹配原始命令字符串中的任何位置,其保护标记即使被引号括起作为数据(提交消息正文、dcg test "..." 探测、here-doc 载荷),仍可能触发,即使实际不会执行任何有害操作。这是“只有纯谓词才能拒绝”这一规则有意接受的代价;另一种方案(查询仓库/上下文)恰好属于该约束禁止用于 deny 的有状态谓词。每次触发都可撤销:使用一次性的 AOP_WAIVE=<policy-id> 或添加一行 policy-waivers 即可解除。

语义:正常路径 = 退出码 0,无输出。deny = 退出码 2 + 一条 stderr 路由提示(每个会话仅首次显示完整消息,之后显示简短提示——每次尝试仍会被拦截)。route = 退出码 0 + permissionDecision:"ask" JSON。audit = 放行并记录。每次触发都会追加一条哈希防护遥测记录(token_class = 策略 ID,并包含 mode/decision)。使用 AOP_WAIVE=<policy-id> 豁免一次,或在 policy-waivers 文件中添加一行 <policy-id> <expiry-epoch>。缺少注册表或 jq 时会放行。

强制执行组(全部为纯正则、高痛点)。前四项属于首日维护者组(age-wnyt)——用于保护本仓库的构件。第五项保护的是产品自身的不变量,因此会在每个使用方仓库中触发,而不仅限于当前仓库:

策略拦截路由至
core.git:add-beads-ledgergit add 指定 _beads/(私有账本一旦泄露就无法挽回)推送账本仓库本身——绝不要在公共目录树中执行 git add _beads
core.provenance:ledger-hand-append重定向/tee/Edit/Write 到 docs/provenance/ledger.jsonl(哈希链式记录,已封存)ao provenance add
core.skills:copy-into-installedcp/rsync/mv 到 `~/.claude.codex
core.skills:edit-installed-copy对已安装技能副本执行 Edit/Write(仅检查 file_path——不会因文本内容触发)编辑仓库中的 skills/<name>/
core.verdicts:hand-edit对 .agents/ao/verdicts/ 执行 Edit/Write,或使用 Bash >/>>/tee/cp/rsync/mv 写入其中(强制检查目标位置)——文件名就是内容的 SHA-256,手动编辑会破坏摘要身份重新运行验证,让它持久化存储新的产物(validate.py store-verdict)

core.verdicts:hand-edit 是唯一一个针对 承诺 而非仓库的策略:如果判定结果的哈希值不再与其文件名相符,它就是伪造证据,而上述机制都无法在工具调用层面捕获这种情况。读取存储内容不受影响——对判定结果执行 cat/ls/jq/rg/diff,以及将其复制出来检查,都不会触发;只有写入存储的操作才会触发——包括原位编辑器(sed -i、perl -pi/-ni)和删除命令(rm、unlink、shred)。匹配基于标志参数标记,因此读取操作的脚本文本仅仅包含 -i 时不会触发(已通过 bats 双向验证)。尚未披露的缺口:noclobber 覆盖重定向(>|)。

如何触达用户——每种安装路径都会部署钩子:

安装路径部署方式
Claude Code 插件(claude plugin install agentops@agentops-marketplace)自动——插件包含 hooks/hooks.json(${CLAUDE_PLUGIN_ROOT} 路径);安装后钩子即处于激活状态,无需接线步骤
npx skills@latest add boshu2/agentops / skills.sh 复制技能包自带安装器:~/.claude/skills/cc-hooks/scripts/install-hooks.sh(一个命令即可;复制文件无法自行接线)
git clone / brew checkoutscripts/install-policy-dispatch.sh(委托给同一个内嵌于技能的安装器)

安装器会在接线前检查注册表,备份设置,并且可以重复运行。可通过 /plugin disable agentops 或从设置中移除两个 PreToolUse 匹配器来按主机禁用。

契约测试:tests/scripts/policy-dispatch.bats(每项策略的拦截+消息+遥测、意外标准输出风险、豁免、审计/路由模式、失败时放行)。

编写自己的钩子

最简 Python:

#!/usr/bin/env python3
import json, sys

data = json.load(sys.stdin)
cmd = data.get('tool_input', {}).get('command', '')

if 'dangerous' in cmd:
    print("Blocked: dangerous", file=sys.stderr)
    sys.exit(2)

sys.exit(0)  # Allow

钩子输入(stdin):

{"tool_name":"Bash","tool_input":{"command":"npm test"},"session_id":"...","cwd":"..."}

环境变量

变量作用范围用途
CLAUDE_PROJECT_DIR全部项目根目录
CLAUDE_ENV_FILESessionStart/Setup持久化环境变量

Stop 钩子(强制继续)

{"decision":"block","reason":"Tests failing. Fix before stopping."}

**重要:**检查 stop_hook_active,以防止无限循环。

反模式

不要应该
旧对象格式使用包含 matcher 的数组格式
不加引号的 $VAR"$VAR"
搭配 JSON 使用退出码 2退出码 2 仅使用 stderr
跳过 stop_hook_active 检查在 Stop 钩子中始终检查

调试

claude --debug  # Hook execution details
/hooks          # View/edit in REPL

输出规范

  • 路径: 用户的 ~/.claude/settings.json 或项目的 .claude/settings.json,以及明确命名的钩子脚本。PreToolUse 策略分发器默认随附(每种安装路径都会接入它——参见“策略分发引擎”);其他防护方案(技能优先协调、独立的已安装技能编辑防护、读取预算防护)在主动启用前均不生效。
  • 文件名: 保留 settings.json;为脚本指定描述性且可执行的文件名,不要将大型 shell 程序嵌入 JSON。
  • 格式: 有效的 Claude 钩子 JSON,使用事件数组、匹配器和命令对象;钩子的标准输出/标准错误和退出码遵循所选事件的架构。
  • 退出码: 使用 jq -e '.hooks | type=="object"' <settings.json> 验证,并针对每个匹配器执行一次有代表性的静默/触发测试;任何解析错误、正常路径产生多余输出或递归风险都会阻止启用。
  • 下游交接: 只有在报告确切范围、撤销命令、测试证据和选择加入位置后,操作员才会使用。

质量检查清单

  • 匹配器会在预期事件/输入上触发,并在有代表性的相似但不匹配的情况下保持静默。
  • 阻止和允许路径使用文档规定的退出码和输出通道,且不会泄露上下文。
  • 钩子可撤销、范围狭窄、不会递归,并明确标记为选择加入的主机策略。

参考资料

评论

支持 Markdown