上周有开发者让 Claude Code 帮忙重构一个模块,它很"贴心"地执行了 rm -rf dist/ 来清理构建产物。问题是,那个目录里还有一份手动调试时放进去、没有提交到 Git 的配置文件——直接没了。这不是 Claude 的错,它按照正常工程流程执行了清理操作。但这次事故揭示了一个真实需求:能不能有一种机制,在危险操作执行之前拦截它,就像 Git 的 pre-commit hook 一样?Claude Code 的 Hooks 就是答案。本文给出 6 个可以直接抄的生产级 Hook 场景。
Hooks 是什么:Agent 生命周期的切面
核心事件:
SessionStart 对话开始/恢复 注入环境变量、加载上下文
PreToolUse 工具执行前 拦截危险操作、修改参数
PostToolUse 工具执行后 自动lint、日志记录
PermissionRequest 权限弹窗时 自动批准/拒绝特定操作
Stop Agent结束响应 阻止过早结束、触发总结
UserPromptSubmit 用户提交prompt 预处理、添加上下文
如果写过 Spring 的 AOP 或用过 Git Hooks,这个概念一秒就懂:在 Agent 执行特定操作的前后,自动触发自定义逻辑。其中 PreToolUse 是最常用的——90% 的"护栏"需求都在这里实现。
三级配置作用域
~/.claude/settings.json 全局:所有项目生效
.claude/settings.json 项目级:随代码提交,团队共享
.claude/settings.local.json 本地:不提交,只对自己生效
推荐做法:通用的安全策略放全局,项目特定的放项目级配置提交到仓库。
场景一:拦截危险 Shell 命令
#!/bin/bash
# .claude/hooks/block-dangerous-commands.sh
INPUT=$(cat)
COMMAND=$(echo "[STDIN_JSON]" | jq -r '.tool_input.command // empty')
DANGEROUS_PATTERNS=(
'rm\s+-rf\s+/'
'git\s+push\s+.*--force'
'DROP\s+TABLE'
'DROP\s+DATABASE'
'git\s+reset\s+--hard'
'>\s*/dev/sd'
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "[COMMAND_VAR]" | grep -iEq "[PATTERN_VAR]"; then
echo "BLOCKED: 命令匹配危险模式 [pattern]" >&2
exit 2
fi
done
exit 0{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "[CLAUDE_PROJECT_DIR]/.claude/hooks/block-dangerous-commands.sh",
"timeout": 5
}]
}]
}
}关键踩坑点:退出码的含义和直觉不同——退出码 1 是非阻塞错误(Claude 会忽略并继续执行),退出码 2 才是阻塞错误(Claude 收到拒绝,停止操作)。很多人第一次写用了 exit 1,结果发现拦截逻辑完全不生效。
场景二:敏感文件保护
PROTECTED_PATTERNS=(
'\.env$' '\.env\.' 'credentials' 'secret'
'\.pem$' '\.key$' 'package-lock\.json$'
'pnpm-lock\.yaml$' 'yarn\.lock$' 'go\.sum$'
)matcher 需同时匹配 Edit|Write 两个工具,防止 Claude 修改 .env、密钥文件、锁文件等不该被动的文件。
场景三:代码修改后自动 Lint
# .claude/hooks/auto-lint.sh,按文件类型选linter
case "[FILE_PATH]" in
*.js|*.ts|*.jsx|*.tsx) npx eslint --fix "[FILE_PATH]" ;;
*.py) python -m ruff check --fix "[FILE_PATH]" ;;
*.go) gofmt -w "[FILE_PATH]" ;;
esac这是 PostToolUse 场景,在编辑完成后触发。如有lint 问题,通过 hookSpecificOutput.additionalContext 把结果作为上下文反馈给 Claude,让它能在同一轮对话里自动修复格式问题。
场景四:SessionStart 自动注入项目上下文
# 每次对话启动自动收集:
BRANCH=$(git rev-parse --abbrev-ref HEAD)
RECENT_COMMITS=$(git log --oneline -5)
DIRTY_FILES=$(git diff --name-only)
ISSUES=$(gh issue list -L 3 --json title,number)有个细节:SessionStart 的 matcher 可以区分 startup(新对话)、resume(恢复对话)和 compact(context 压缩后),只在 startup 时加载完整上下文,避免 resume 时重复注入。
场景五:异步审计日志
{
"hooks": {
"PostToolUse": [{
"hooks": [{
"type": "command",
"async": true,
"command": "echo \"$(date) | $(jq -r '.tool_name') | ...\" >> audit.log"
}]
}]
}
}关键是 "async": true——异步执行,不影响 Agent 速度。没有 matcher 意味着所有工具调用都会被记录,出问题时这份日志能帮你回溯 Claude 的每一步操作。
场景六:HTTP Hook 对接外部合规系统
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "http",
"url": "http://localhost:8080/api/validate-command",
"headers": {"Authorization": "Bearer [COMPLIANCE_TOKEN]"},
"allowedEnvVars": ["COMPLIANCE_TOKEN"],
"timeout": 10
}]
}]
}
}HTTP Hook 把工具调用的完整 JSON 作为 POST body 发送给企业内部服务,服务返回 {"decision": "block", "reason": "..."} 即可拦截操作。这在企业环境里特别有用——安全团队维护一个中心化策略服务,所有开发者的 Claude Code 实例都通过 HTTP Hook 对接。
四种 Hook 类型怎么选
command 本地Shell脚本 大多数场景,开销极低
http HTTP POST请求 对接外部系统,取决于网络
prompt 发送给LLM评估 需要语义理解的判断,开销较高
agent 启动Sub-agent 需要多步推理的复杂验证,开销最高
90%的场景用command就够了,prompt/agent类型不建议
放在高频事件(如PostToolUse)上
常见问题排查
Q: Hook脚本stdin里传了什么?
A: 不同工具结构不同,Bash传tool_input.command,
Edit传file_path/old_string/new_string,公共
字段含session_id、cwd、permission_mode
Q: 怎么调试Hook?
A: 输入/hooks查看已加载配置;开发时先在终端单独
测试脚本:
echo '{"tool_input":{"command":"rm -rf /"}}' | \
bash .claude/hooks/block-dangerous-commands.sh
Q: Hook会拖慢执行速度吗?
A: 同步Hook会。设置合理timeout(默认600秒太长,
5-10秒足够);不需要阻塞的操作用async:true
实战建议
1. 最小化起步配置:先上PreToolUse危险命令拦截+
敏感文件保护这两个高价值场景,覆盖大部分安全
风险
2. 团队协作项目务必把安全类Hook提交到.claude/
settings.json随代码共享,而非只放本地配置
3. 编写拦截脚本时反复检查退出码逻辑,exit 2才是
真正拦截,这是最容易踩的坑
4. 审计日志类Hook务必加async:true,避免拖慢正常
工作流程
5. 企业环境优先考虑HTTP Hook对接现有安全审核
服务,比给每个开发者维护本地脚本更好治理
总结
Hooks 本质上是给 AI Agent 加 middleware。和 Web 开发里的中间件一样,最好的 Hook 是你写完就忘了它存在——它在背后默默工作,只在真正危险的时候跳出来拦你一下。从"信任 Agent"到"信任但验证",这正是 Hooks 存在的意义。
来源:Claude Code Hooks 2026 完整实战指南 — 腾讯云开发者社区