检查门不靠自觉,Claude Code Hooks 第一条这样配

检查门不靠自觉,Claude Code Hooks 第一条这样配

用一个场景开场。你让 Claude Code 改一段代码,它思考两秒,屏幕上滚出一行命令,rm -rf 加一个路径。你的手悬在回车键上方,心里冒出一个问题,这一跑,删的是它该删的吗。

这种手悬半空的时刻,就是这篇文章要解决的问题。Claude Code Hooks,一组在固定时刻自动运行的命令,Claude 准备读文件、改文件、跑命令、结束回答的前后,检查门先执行你设好的检查,决定放行、拦截,还是顺手记一笔。

它是检查门,不是让 AI 更聪明的开关

先把定位摆正。Hooks 和提示词的根本区别是确定性。提示词像口头约定,你说「不要做危险操作」,它多数时候记得,但它终究在理解你的话,理解就有失手的时候。Hooks 是程序,到那个时刻一定执行,不靠模型临场想起来。Anthropic 官方文档对它的定位就是这个意思,为 Claude Code 的行为提供确定性控制,确保某些操作始终发生,而不是依赖模型选择去运行。

所以别把它当成让 AI 变聪明的开关,它不让 AI 更聪明,它让一部分关键动作变得更可控。这两件事差得很远,想清楚就不会一上来配一堆花哨的自动化。

什么时候需要它。答案很简单,当你开始让 Claude Code 真正动手改文件、跑命令,而不只是问它问题时。会动手就有动手前后的风险,而你不可能全程盯着屏幕。它最适合放在高风险、重复、容易忘的地方,危险命令、发布动作、写入线上配置、改完代码后的检查提醒。至于回答要简洁、语气要自然这类表达层面的要求,继续写在 CLAUDE.md 里就够,不用动用 Hooks。

分工判断老雷用一条标准,犯一次也可以接受的,用文字规则,犯一次代价很高的,用 Hooks。能靠理解解决的让它理解,必须有硬边界的才上检查门。

八个触发点不用背,按时间线记

新手不用记八个英文名,只要记住它们发生在一条时间线上的位置,会话开始,你提交问题,工具动手前,工具动手后,回答收尾,子任务结束,上下文压缩前,会话结束。知道问题发生在哪一刻,就知道该配哪个钩子。

对应的名字顺一遍就是 SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、Stop、SubagentStop、PreCompact、SessionEnd。Claude Code 的生命周期事件其实已经有几十个,但这八个覆盖了新手绝大多数场景。

第一天只记住两个就够。动手前的 PreToolUse 是门卫,在它出门干活前拦一下看证件,用来挡风险。动手后的 PostToolUse 是验收员,等它干完活回来检查成果,用来提醒验收、顺手格式化改过的文件。这一进一出安排好,其他岗位以后再说。

按时间线记还有个好处,能定位配置错误。很多配错的情况,根子是触发时机想错了,想在动手前拦,却去改了收尾事件,脚本写得再对也不会在你期待的时刻出现。

第一条放项目级,长这样

配置文件有四个层级,从上到下是企业策略管整个组织,家目录下的全局配置管你所有项目,项目根目录的 .claude/settings.json 管当前项目且能提交进 git,旁边的 settings.local.json 管当前项目但不进版本库。

第一条放项目级,不要图省事写全局。新手最容易把一个项目的临时规则带到所有地方,某个项目要求改完跑一个很慢的检查,写进全局,其他项目全被拖慢。全局只该留非常稳定的个人习惯,比如统一的通知方式。

动手前先用 Claude Code 里的 /hooks 命令看一眼当前注册了什么。注意它是只读的,能看不能改,添加和删除要直接编辑设置文件。

一条 hook 的结构就三层,最外层按事件名分组,事件下用 matcher 限定在哪些工具触发,最里层写真正要跑的命令。比如每次 Claude 用 Edit 或 Write 改完文件,自动跑一遍格式化。

{

"hooks": {

"PostToolUse": [

{

"matcher": "Edit|Write",

"hooks": [

{

"type": "command",

"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"

}

]

}

]

}

}

如果不知道第一条该写什么,从危险命令开始。事件触发时,Claude Code 会把这次操作的数据以 JSON 形式送进脚本的标准输入,工具名在 tool_name,对 Bash 命令来说,要执行的命令就在 tool_input.command 里。下面这个脚本读出命令,发现危险关键词就以 exit 2 退出,原因写到 stderr,Claude 会收到这段原因并放弃这次操作。

#!/bin/bash

INPUT=$(cat)

COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -qE 'rm -rf|drop table'; then

echo "已拦截,检测到高风险命令,请人工确认后再执行" >&2

exit 2

fi

exit 0

退出码的含义就三种。exit 0 表示不表态,操作照常走正常权限流程,对 PreToolUse 来说不等于批准。exit 2 表示阻断这次操作,stderr 的内容反馈给 Claude 让它调整。其他退出码,操作继续,终端显示一条报错提示。

脚本存成 .claude/hooks/block-danger.sh,加上可执行权限,再用 PreToolUse 注册它,matcher 填 Bash 就能用了。

极简三层木架示意一条配置的三层嵌套,顶层一格贴事件名木牌,中层一格贴匹配器木牌,底层一格贴命令纸条,侧边一小格空置的项目级格子被点亮

少配,先提醒,还要演练

这里有几条反直觉的经验,都是前人踩出来的。

第一,别一上来写很多脚本。hook 写多了 Claude Code 会变慢,而且你会分不清是哪一条在影响结果,它没跑某条命令,是它自己判断不跑,还是被某条 hook 拦了。经验值是单条 hook 控制在一秒内,总数先压在十个以内,第一周三条以内。判断一条 hook 该不该留有个朴素标准,它触发时,你是否愿意停下来读它。你自己都不想读,它就只是噪音。

第二,新手阶段先提醒不硬拦。很多人第一条就追求全自动强制拦截,脚本写复杂了误拦正常命令,几天后嫌烦全关掉。老雷的建议是 exit 2 之前先想清楚,提醒内容要写给人看,说清为什么拦、该确认什么。一句 blocked by hook 等于没说。

这里的重点不是让 Claude 永远不能做危险事,而是让危险事不能悄悄发生。真要删除、发布、改配置时,你仍然可以手动确认,自动化最怕的不是慢一点,是快到来不及看。

第三,每加一条都要真实演练。配置写好不等于生效,可能从另一个目录启动 Claude Code,它压根没读到那份配置。演练用临时目录和临时文件,不碰真实项目,让 Claude 做一个低风险动作,看三件事,hook 是否在正确时机出现,提醒内容是否看得懂,任务是否还能继续。任何一步不清楚,就先别放进真实项目。

起步节奏可以按三天走。第一天只做危险命令提醒,目标是敢把它放进项目做小改动。第二天加一个改文件后的检查提醒,先提醒就够,养成改完要验的节奏。第三天再看要不要会话开场提醒,比如自动把当前目录规则塞回上下文。三天做完没有明显痛点就停,能让你少犯一个真实错误的规则才值得留。

极简插图,一只火柴人守在窄门口,先递出一张黄色提醒便签而不是关死木门,门缝里透出一条亮着的通道

出问题先看三处

hook 不生效是新手最常撞上的事,先按固定顺序排,别一上来钻脚本细节。

第一处,有没有注册。用 /hooks 看当前配置,很多问题是配置根本没被读到,JSON 多写了逗号、文件放错位置,都算这类。第二处,触发时机对不对。想动手前拦就看工具使用前的事件,想改完提醒就看工具使用后的事件,位置错了脚本再对也白搭。第三处,脚本是不是太重。慢检查、联网检查、长时间扫描都会拖慢节奏,能轻就轻,能异步就异步。

三处都没问题还不行,用官方推荐的笨办法,在终端里手动把一段示例 JSON 用管道喂给脚本,看退出码和输出对不对,比对着配置干猜快得多。

几个高频问答收个尾。exit 2 和结构化 JSON 两条拦截路别混用,返回 exit 2 时 Claude 会忽略你打的 JSON,简单拦截用退出码,要在拦、放行、转人工之间做选择才用带 permissionDecision 的 JSON。改文件格式化只对 Edit 和 Write 生效,它用 Bash 里的 sed 或重定向改的文件不会触发,真遇到漏网再补一个 Bash 的 matcher 或用 Stop hook 收尾扫一遍。报 command not found 多半是路径问题,用绝对路径或 $CLAUDE_PROJECT_DIR,报 jq 不存在就装 jq,脚本没跑先查可执行权限。团队想统一治理还有 HTTP 钩子,把事件 POST 到远端服务,适合审计合规场景,个人和小团队本地脚本够用就别引入远程依赖。

最后说下适合谁。如果你只是偶尔让 Claude Code 解释代码,先把提问方式和项目规则写清楚,收益更直接。只要开始把它当会动手的同事用,Hooks 就值得学。团队场景尤其需要,不是每个人都会记得同一套口头约定,把底线变成统一动作,密钥不进仓库、不绕过测试发布、不动线上配置,这些大家都认的底线才值得写成 hook,什么都想管的焦虑清单只会被绕过。

每个项目的第一条 hook,应该来自这个项目最真实的风险。

读配置之前先问自己一句,我最怕哪个动作出错,是误删、误发、误改配置,还是改完不验证。答案不同,第一条就不同。写下最近一次你不放心 Claude Code 的瞬间,把它翻译成一条检查门,这样配出来的东西才服务你的工作,不是服务一份看起来很完整的清单。

极简三段台阶排查图,火柴人沿三级浅台阶向上走,每级台阶侧面分别挂着放大镜、时钟与羽毛的极简剪影