当 agent 拥有读取文件、运行 shell、修改代码和 ship PR 的权限时,prompt 里的一句提示远远不够。需要一层在 tool 真正运行之前进行检查的机制。
你交给 agent 一件小事:修复某个模块中的 validation。它为了更稳妥,多扫了几个目录,打开了敏感的 config 文件,然后顺手把一些不相关的 cleanup 也塞进同一个 PR。
表面上一切正常,测试也通过了。但 context 已混入无关文件,secret 可能已进入 transcript(你与 agent 之间对话的完整记录),而 review 也因为待修改部分和「顺手」部分混在同一个 PR 里而被稀释。
cat .env——secret 进入 transcript.env——privacy-block 询问用户Agent 不会自然而然地变得更谨慎。只是在它做某件事之前,有一层机制站出来问:这件事应该被允许运行吗?
Hook 是真正的拦截层。
Guard rail 不会让模型变得更聪明。它在 tool 运行前检查行为。Rule、hard-gate 和 guard skill 是额外的引导层,有用但仍依赖模型或用户是否调用。
I. 概念
Guard rail 是介于 agent 与操作之间的一层:有风险的操作予以拦截,可疑的操作发出警告。它就像楼梯边缘的护栏:不会让你走得更好,但能降低踩空变成事故的概率。与 prompt 中的提示语(prompt instruction)不同,guard rail 运行在工具调度层,不依赖模型是否「记住」。
| Guard rail | 提示语 | |
|---|---|---|
| 运行位置 | Harness,在模型之外 | 在 context 内,由模型读取 |
| 强制执行 | 代码硬性拦截 | 模型自愿遵守 |
| Context 过长 | 仍然运行 | 容易遗忘/跳过 |
| 模型理解偏差 | 仍会拦截(若不 crash) | 随模型偏差 |
| 修改方式 | 需改代码/config | 改文字即可 |
Harness 是包裹模型的运行时层。模型只决定「我想读这个文件」或「我想执行这条命令」;harness 才是接收 tool call、管理权限、调用 hook、决定让 tool 运行或拦截、并将结果返回给模型的一方。Hook 是 harness 在 tool call 生命周期的固定时间点自动调用的小脚本——收到 prompt 时、tool 执行前、tool 执行后——用于介入检查。由于所有真实操作都经过 harness,这里是挂载 guard rail 的地方。
概念背景延伸阅读:Duy /zuey/ 的 Harness Engineering là gì?。
ClaudeKit Hooks 的更多细节可见 VividKit Guides。
Hook 不在模型内部。它位于 Claude Code 的生命周期中:settings.json 决定调用哪些 hook,.ck.json/ENV 决定运行时行为,hook 的输出可以允许运行、拦截、注入 context,或在 tool 或 session 结束后写入 state/artifact。
关键点:pre-tool hook 返回 exit code。Exit 0 允许 tool 运行,exit 2 拦截并将原因推送给模型。流程中三个主要介入点有对应的技术名称:UserPromptSubmit(收到 prompt 时)、PreToolUse(tool 执行前)、PostToolUse(tool 执行后)。Stop/SubagentStop 在 session 或 subagent 结束时运行,因此位于上方的生命周期映射图中。
Exit code 是进程结束时返回的数字。Claude Code 通过它决定 tool 是否运行:
exit 0——正常,tool 运行。stdout 作为 JSON output 读取。exit 2——拦截。丢弃 stdout,将 stderr 推送给模型作为错误消息。exit 1(或其他代码)——报错但不拦截。报告错误后 tool 仍继续运行。只有 exit 2 才是真正的拦截。想要 enforce policy 的 hook 必须使用 exit 2——按 Unix 习惯误用 exit 1 会导致 guard 看似开启实则放行。
Hook exit 2,tool 不运行。但 hook crash 时 → Claude Code 放行(fail-open)。Hook 有 bug 会导致 guard 静默关闭。
向 context 添加文本(rule、hard-gate)。模型需自行遵守。强弱取决于模型。
.env、扫描范围过宽,或将 scope 外的 cleanup 拉入 PRII. CK 中的实现
全新 CK 安装后,文件访问 guard 位于 PreToolUse hook,如 scout-block 和 privacy-block。若当前 Claude Code session 以跳过权限提示的模式运行,这些 hook 仍是 CK 的主要拦截层。但 CK 的 hook 是 fail-open 的:crash 或被禁用时,tool call 可能照常通过。
| 分组 | 机制 | 示例 | 强制执行 |
|---|---|---|---|
| 文件/路径拦截 | PreToolUse | scout-block | 代码 · fail-open |
| 步骤错误拦截 | UserPromptSubmit | simplify-gate | code |
| 注入 context | UserPromptSubmit | dev-rules-reminder | code |
| 保持命名规范 | Pre/Post/Stop | descriptive-name | 代码 · 提示 |
| Hard-gate skill | XML markdown | <HARD-GATE> | 提示语 |
| Rule 提示语 | CLAUDE.md | review-audit | 提示语 |
| Guard skill | 用户调用 | ck:security-scan | user |
scout-block、privacy-block · Section 05-06simplify-gate、workflow gate · Section 07-08dev-rules-reminder 将文本注入 prompt · Section 10descriptive-name · Section 04 的 hook 表格<HARD-GATE> · Section 09CLAUDE.md rules 是被注入的内容 · Section 10ck:security-scan、ck:ship · Section 11| 场景 | 处理层 |
|---|---|
读取 node_modules/react/ | scout-block,exit 2 |
读取 .env 检查 key | privacy-block,exit 2 + approval prompt |
在根目录使用 **/*.ts glob | scout-block broad-pattern |
| diff 已膨胀时触发 ship prompt | simplify-gate,若 gate.enabled=true |
| 没有 plan/review 就开始写代码 | ck:cook HARD-GATE |
| 修改用户已确认的 threshold | review-audit rule:修改前先询问 |
| 新建名称模糊的文件 | descriptive-name, PreToolUse(Write) |
| Plan 使用格式错误的链接/文本 | plan-format-kanban, PostToolUse(Edit/Write/MultiEdit) |
| 两个 teammate 同时修改同一文件 | team-coordination rule,仅限 Agent Team |
两层 config 决定哪些 guard 运行:settings.json 将 hook 挂载到 Claude Code 的生命周期;.ck.json 逐个启用/禁用 hook 并调整 threshold。
# Global scope
~/.claude/settings.json
~/.claude/.ck.json
~/.claude/hooks/*.cjs
# Project scope
.claude/settings.json
.claude/.ck.json
.claude/hooks/*.cjs
以下片段仅展示全新 CK 安装中 PreToolUse 的一个切面:CK 提供 scout-block 和 privacy-block,并将它们连接到生命周期,以便 Claude Code 在 tool 运行前调用。已安装机器上的完整 hook config 包含更多生命周期事件;需要查看的地方是 Claude Code 的 settings.json。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Glob|Grep|Read|Edit|Write",
"hooks": [
{ "command": "node \".claude/hooks/scout-block.cjs\"" },
{ "command": "node \".claude/hooks/privacy-block.cjs\"" }
]
}
]
}
}
settings.json 是生命周期映射:用户发送 prompt 时调用哪个 hook,tool 运行前调用哪个 hook,tool 完成后由哪个 hook 继续处理。全局安装时,CLI 会将模板中的相对命令替换为 node "$HOME/.claude/hooks/scout-block.cjs" 形式;项目安装时,路径通常保持 .claude/hooks/... 形式。
不要把所有内容归为「默认开启/关闭」。一个 hook 的状态有三个不同层面:
| 标签 | 审计时的含义 |
|---|---|
script file | .cjs 文件是否位于 .claude/hooks/。有文件不代表 hook 正在运行。 |
wired | settings.json 已将脚本挂载到 Claude Code 的生命周期事件。未 wire 则 Claude Code 不会自动调用该 hook。 |
runtime flag | 当 hook 被调用时,内部代码读取 .ck.json/DEFAULT_CONFIG/ENV 来决定是否继续执行。部分 guard 还有子开关,如 privacyBlock 或 simplify.gate.enabled。 |
下表是 stable claudekit-engineer@2.19.1 的 hook 列表快照。2026-06-09 更新:claudekit-engineer@2.19.2-beta 准备移除 task-completed-handler 和 teammate-idle-handler;当 upstream ClaudeKit 发布新版本时,列表可能变为 14 个 hook。
session-initwired启动、恢复、clear、compact 时的项目/环境初始化。
usage-quota-cache-refreshwired不读取独立 hook flag;wire 后即运行。
simplify-gategate off脚本被调用,但 simplify.gate.enabled 默认为 false。
dev-rules-reminderwired注入 dev rules/context。
subagent-initwired为 subagent 注入 context。
descriptive-namewired提示为文件/脚本取有意义的名称。
scout-blockwired拦截重型目录和过宽的 glob。
privacy-blockwired拦截 secret 路径,请求 approval。
plan-format-kanbanwired不读取独立 hook flag;警告 plan 格式。
session-statewired写入 session/task 状态。
cook-after-plan-reminderwiredDEFAULT_CONFIG.hooks 中无独立 key,但 isHookEnabled() 仅在 flag 为 false 时禁用。
workflow-artifact-gatenot wiredartifact gate 需 opt-in:须 wire hook 并启用 flag/gate config。
task-completed-handlerremovingAgent Teams task completed;2.19.1 之后不应视为稳定 hook。
teammate-idle-handlerremovingAgent Teams teammate idle;2.19.1 之后不应视为稳定 hook。
team-context-injectnot wiredDEFAULT_CONFIG.hooks 中无独立 key;仅在 workflow/team 层调用时有意义。
usage-context-awarenessnot wiredUsage/context injection hook;不在 settings.json 模板中。
容易忽略的点:isHookEnabled() 只在 hooks.<name> 为 false 时禁用。缺少 key 通常被视为 enabled。但部分 guard 还有独立开关:privacy-block 仍读取旧 key privacyBlock=false,simplify-gate 有 simplify.gate.enabled 和 ENV CK_SIMPLIFY_DISABLED=1。
如需快速调整,在目标 scope 修改 .ck.json。当 key 明确设置时,项目 config 优先于全局;缺少 key 时继续 inherit/默认。对于已在 settings.json 中 wire 的 hook,设为 false 即禁用,设为 true 可在上层 scope 禁用后重新启用。Hook 列中的 hook 名称也是 hooks.<name> 中使用的 key。
// .claude/.ck.json 或 ~/.claude/.ck.json
{
"hooks": {
"scout-block": false,
"privacy-block": true
},
"privacyBlock": true,
"simplify": {
"gate": {
"enabled": true
}
}
}
| 操作目标 | 修改位置 | 说明 |
|---|---|---|
| 禁用一个已 wire 的 hook | .ck.json {"hooks":{"scout-block":false}} | Hook 脚本仍存在,但运行时放行。 |
| 重新启用已在 global 禁用的 hook | project .ck.json {"hooks":{"scout-block":true}} | 本地明确的 key 覆盖全局。 |
| 禁用 privacy guard | .ck.json {"hooks":{"privacy-block":false}}.ck.json(旧 key) {"privacyBlock":false} | privacyBlock=false 是旧 key,但仍有效。 |
| 真正启用 simplify-gate 拦截 | .ck.json {
"hooks": {
"simplify-gate": true
},
"simplify": {
"gate": {
"enabled": true
}
}
} | 缺少 simplify.gate.enabled=true 则 gate 未开启拦截模式。 |
| 按 session/scope 禁用 simplify-gate | settings.json {
"env": {
"CK_SIMPLIFY_DISABLED": "1"
}
} | ENV 覆盖优先于 config gate。 |
| 启用 workflow-artifact-gate | settings.json wire UserPromptSubmit / PreToolUse(Bash).ck.json {"hooks":{"workflow-artifact-gate":true}} | 全新安装未预置 wire,仅修改 <code>.ck.json</code> 不够。 |
想知道 hook 是否被触发,看生命周期事件而非文件名。同一 hook 脚本只有当 settings.json 中的 event/matcher 与当前操作匹配时才运行。下表是快速检查每个 hook 的方法;not wired 的 hook 需先 wire 或通过手动 CLI 调用(若脚本支持)。
| Hook | 手动触发方式 | 备注 |
|---|---|---|
session-init | 打开、恢复、clear 或 compact Claude Code session。 | SessionStart(startup|resume|clear|compact)。 |
usage-quota-cache-refresh | 打开 session、发送新 prompt,或更新 Task/Todo。 | 缓存用量;不读取独立 hook flag。 |
simplify-gate | 发送含 ship/merge/pr/deploy/publish 意图的 prompt,且 diff 足够大时。 | 需要 simplify.gate.enabled=true;默认不拦截。 |
dev-rules-reminder | 发送新 prompt。 | UserPromptSubmit;按 TTL 注入 rules。 |
subagent-init | 通过 Task/agent flow 启动 subagent。 | SubagentStart. |
descriptive-name | 让 Claude 通过 Write 创建文件。 | PreToolUse(Write);提示文件名清晰。 |
scout-block | 让 Claude 读取 node_modules、dist,或使用过宽的 glob。 | PreToolUse 作用于 Bash/Glob/Grep/Read/Edit/Write。 |
privacy-block | 让 Claude 读取 .env、key 文件、secret 路径。 | PreToolUse;附带 privacyBlock。 |
plan-format-kanban | 让 Claude 对 plan 文件执行 Edit/Write/MultiEdit。 | PostToolUse;警告格式,不读取独立 hook flag。 |
session-state | 更新 Task/Todo、结束 subagent,或结束当前 turn。 | PostToolUse、SubagentStop、Stop。 |
cook-after-plan-reminder | 让 Plan subagent 结束。 | SubagentStop(Plan);缺少 key 时仍为 enabled。 |
workflow-artifact-gate | Wire 到 UserPromptSubmit/PreToolUse(Bash),或以 --stage 运行脚本。 | 全新安装不会自动触发 hook 模式。 |
task-completed-handler | 在 Agent Teams 中完成任务。 | TaskCompleted;准备在 claudekit-engineer@2.19.2-beta 中移除。 |
teammate-idle-handler | 让 Agent Teams 中的 teammate 完成工作并进入 idle 状态。 | TeammateIdle;准备在 claudekit-engineer@2.19.2-beta 中移除。 |
team-context-inject | Wire 到 SubagentStart,然后启动 team subagent。 | 当 agent id 属于 team 时脚本才有意义。 |
usage-context-awareness | Wire 到目标事件,或使用已 wire 的 hook cache 刷新。 | usage quota cache 刷新的遗留 wrapper。 |
// ~/.claude/settings.json 或 .claude/settings.json
{
"env": {
"CK_SIMPLIFY_DISABLED": "1"
}
}
Agent 经常读取 node_modules/、在根目录使用 **/*.ts glob(glob 是路径匹配模式;**/*.ts 表示所有子目录中的所有 .ts 文件)、cat dist/index.js。每次都会将数万 token 塞入 context,拉高成本并降低后续 turn 的质量。scout-block.cjs 注册 PreToolUse,在所有 Read/Bash/Glob/Grep 之前运行。
# Baseline .ckignore——由 pattern-matcher.cjs 处理
node_modules
dist
build
.next
.nuxt
__pycache__
.venv
venv
vendor
target
.git
coverage
构建命令被放行。npm build、go build、make、docker build 等命令仍可运行,即使构建过程涉及 node_modules 或 dist。若拦截这类命令,CK 的 ship/test 流程很容易在构建阶段就自行卡住。
Fail-open:任何解析错误都会导致 exit 0,tool 被放行。Hook 有 bug → 该层 guard 静默关闭。
Agent 接触 .env、id_rsa、*.pem、credentials.yaml——secret 会泄露进 transcript。一旦进入 context,可能被记录、在 reviewer output 中被引用,或粘贴到 PR 中。
@@PRIVACY_PROMPT_START@@
{ "type": "PRIVACY_PROMPT", "question": {...}, "options": [...] }
@@PRIVACY_PROMPT_END@@
Claude 解析 JSON,调用 AskUserQuestion。若用户批准,hook 消息会引导通过已批准路径读取文件,例如在询问后执行 cat ".env";代码也支持 APPROVED: 前缀。核心点:必须有明确的 approval,不能让模型自行解读 consent。
privacy-block 对 Bash 放行,仅发出警告。任何有 Bash 运行权限的人都可以不经 approval flow 直接读取 secret。
Privacy-block 只拦截 READ。立即轮换凭证,审计对话日志,检查 git history 确认 secret 是否已被 commit。
一个小任务可能膨胀成触及过多文件的 PR:修复 validation、添加 cleanup、改格式、调整几个相邻 helper。当 agent 说「OK ship」时,simplify-gate.cjs 注册 UserPromptSubmit,读取 git diff HEAD,仅在 prompt 包含以下内容时判断:ship merge pr deploy publish。
simplify.threshold 的默认值:总新增+删除行数、触及文件数、单个文件中的最大新增行数。只有在 simplify.gate.enabled=true 时 gate 才会拦截;这些值可在 .ck.json 中覆盖。
hooks.simplify-gate = true 只是让 Claude Code 调用脚本。若要脚本真正拦截膨胀的 PR,还需启用 simplify.gate.enabled = true。全新安装未启用此 flag,因此 gate 只进行轻度检查,不会 block。
// project .ck.json——真正启用
{ "simplify": { "gate": { "enabled": true } } }
matchedSeverity() 会忽略 "don't ship"、"ship on"——"ship on Friday" 也会通过。若要在该 scope 的所有 session 中关闭 gate,在 settings.json 的 env key 下设置 CK_SIMPLIFY_DISABLED=1。
完整 pipeline → 每个 phase 留下一个记录决策的 JSON 文件,如同每步之后的收据。此 hook 设计用于挂载到 UserPromptSubmit + PreToolUse(Bash),但不在全新安装后默认运行的 hook 集中;需要使用须 opt-in。
| Artifact | Phase | 内容 |
|---|---|---|
context-snippets.json | scout/plan | 已读取的代码片段 |
risk-gate.json | predict | 高风险 flag,auto-stop |
verification.json | fix/cook | 5 项检查清单 |
review-decision.json | code-review | Reviewer 结论 |
adversarial-validation.json | adversarial | 对抗性检查 |
此 gate 有两种处理级别。在 ship、push、PR 或 deploy 等高风险步骤,hook 可立即停止流程并将原因返回给模型(emitBlock())。在 finalize 或 commit 等较轻步骤,hook 让流程继续但向 context 添加警告供模型自行修正(emitSoft())。
workflow-artifact-gate 是最强的 artifact 检查层,但全新安装不会自动调用它。使用前须在 settings.json 中 wire hook 并在 .ck.json 中启用 config;否则 ship/push/PR/deploy 不会被 gate 拦截。
Hook 只拦截 tool call。有一类错误不是 tool call,而是流程顺序:在 plan 前写代码,在 scout 前 fix。CK 在 skill markdown 中嵌入 <HARD-GATE>。
<HARD-GATE> Do NOT write implementation code until a plan exists and has been reviewed. Exception: --fast skips research but still requires a plan step. User override: if user explicitly says 'just code it', respect their instruction. </HARD-GATE>
ck:cook 和 ck:fix 各有 4 个 hard-gate(plan/scout-first、exact-req 或 root-cause、no-side-effects)。
「hard」这个词容易误解——并非代码硬性拦截。它是 prompt 中的提示语。模型忽略时没有 exit 2 拦截。XML 包裹方式比普通 rule 产生更强的信号,使模型更难合理化跳过步骤的行为。
dev-rules-reminder.cjs 注册 UserPromptSubmit,将 rules 文本注入每个 prompt。这里需要区分两层:hook 是注入 context 的机制,而 CLAUDE.md 中的 rule 是被注入的内容。按 (sessionId, baseDir) 设置 5 分钟 TTL 以避免 token 消耗。
| 文件 | 防范 | 特征 |
|---|---|---|
review-audit-self-decision | audit 反转已确认的决策 | verified sticky |
development-rules | 跳过测试、使用假数据 | YAGNI/KISS/DRY |
team-coordination-rules | 两个 agent 同时编辑 | ownership glob |
commit-messages | 冗长的 commit body | single-line |
orchestration-protocol | 传递完整历史记录 | context isolation |
Rules 是 context 中的文本,不是代码。若用户 prompt 覆盖,Claude 可能偏离。但易于添加/修改。更适合风格约定,而非安全性 guard。
| Skill | 使用时机 | 检查内容 |
|---|---|---|
ck:security-scan | Pre-release | Secret、CVE、SQLi、XSS、path traversal |
ck:predict | 高风险 feature 前 | 5 个 persona,GO/CAUTION/STOP |
ck:scenario | Pre-impl | 12 维度边界案例扫描 |
ck:ship | Pre-PR | 测试失败即停止,永不 force-push |
code-reviewer agent 运行 9 项检查清单:并发、error boundary、API contract、向后兼容、输入验证、auth/authz、N+1、数据泄露、事实核查。合并前的最后一道 guard。
III. 局限与实践
scout-block/privacy-block,而非针对 secret/重型目录的独立 permissions.deny 列表失控的 agent 可能调用数千次 Read/Glob/Bash。CK hook 不计数 tool call,不会在接近 quota 时自动停止。Quota/cost 限制由 Claude Code、provider 或账户计费决定,不在这层 guard rail 的职责范围内。
gate.enabled = true.ckignore override.env:仔细阅读,不要自动确认settings.json 当前将哪些 hook 挂载到哪些生命周期?.ck.json 禁用了哪些 hook?(outer + inner config).ckignore override?node_modules 是否被 ! 加入 allowlist?| 术语 | 简明释义 |
|---|---|
| harness | 模型与机器之间的调度层:发送 tool 命令、管理权限、运行 hook。是挂载 guard rail 的地方。 |
| hook | Harness 在 prompt/tool/session 生命周期固定时间点自动调用的小脚本,用于介入检查或写入 state。 |
| lifecycle event | Hook 被调用的时间点:UserPromptSubmit(收到 prompt)、PreToolUse(tool 执行前)、PostToolUse(tool 执行后)、Stop/SubagentStop(session/subagent 结束)。 |
| exit code | 进程结束时返回的数字。0 放行,2 拦截,1 报错但不拦截。 |
| fail-open | 当 hook 报错/crash 时,harness 让 tool 继续运行而非拦截。Guard 静默关闭。 |
| transcript | session 中用户与 agent 之间对话的完整记录。 |
| context | 模型推理时「看到」的信息区域。充斥无用文件 → 成本高、质量下降。 |
| glob | 路径匹配模式。**/*.ts = 所有子目录中的所有 .ts 文件。 |
| secret | 敏感信息:API key、private key、credential。泄露进 transcript 即为风险。 |
| LOC | Lines of code——代码行数,用于衡量 diff 大小。 |
| artifact | 每个 pipeline phase 留下的 JSON 文件,记录决策供后续 phase 检查。 |
当 agent 拥有广泛权限时,不要下结论「安装了 CK 就安全了」。在相信 guard rail 之前,检查两处:settings.json 当前 wire 了哪些 hook,.ck.json 当前启用/禁用了什么。
每层防护针对一类风险:hook 拦截 tool call,rule 提示行为,hard-gate 维护流程,guard skill 仅在被调用时运行。了解哪层正在运行、哪层只是提示语,有助于在错误进入 PR 并流向 PROD 之前在正确的地方进行审计。
如果你正在构建 guardrails 系统,可以参考 ClaudeKit 中已经应用的 patterns。 如果你正在考虑购买 ClaudeKit,想直接使用 hooks、skills 和 workflow guard rails,而不是手动拼装每一层,可以通过这个 referral link 购买并享 20% 折扣。
获取 20% 折扣