Claude Code Hooks:让 AI 编程有迹可循、可审计、可回滚
- 1
- 2
- 3
- 4
- 5
- 6
- 7 Claude Code Hooks:让 AI 编程有迹可循、可审计、可回滚
- 8
- 9
大多数人用 Claude Code 的方式是:给指令,看结果,如果不对就撤销。
这能工作,但有一个隐患:你不知道 AI 做了什么,直到它做完。
Claude Code 的 Hooks 系统解决了这个问题。它让你能在 AI 执行每一步操作的前后注入自定义逻辑——不只是”允许还是拒绝”,而是完整的可编程拦截层。
Hooks 是什么
Hooks 是 Claude Code 在特定事件发生时自动执行的 shell 命令。
目前支持四种 Hook 事件:
| Hook | 触发时机 |
|---|---|
PreToolUse | AI 调用任何工具之前 |
PostToolUse | AI 调用任何工具之后 |
Notification | Claude Code 发出通知时 |
Stop | Claude Code 完成响应时 |
每个 Hook 可以:
- 接收 工具调用的完整上下文(工具名、参数、结果)
- 执行 任意 shell 命令
- 影响 工具调用的行为(PreToolUse 可以阻止执行)
配置方式
Hooks 在 ~/.claude/settings.json 里配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/pre-bash.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/post-write.sh"
}
]
}
]
}
}
matcher 支持工具名(Bash、Write、Edit、Read 等)或 *(匹配所有工具)。
实战用例一:危险命令拦截
最常见的需求:阻止 AI 执行某些高风险操作。
~/.claude/hooks/pre-bash.sh:
#!/bin/bash
# 读取工具调用的 JSON 输入
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# 拦截危险操作
DANGEROUS_PATTERNS=(
"rm -rf /"
"git push --force"
"DROP TABLE"
"chmod 777"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "🚫 Hook 拦截:检测到危险命令模式 '$pattern'" >&2
# 退出码 2 = 阻止工具执行并显示错误
exit 2
fi
done
exit 0
Hook 的退出码决定行为:
0— 允许执行1— 记录警告但允许执行2— 阻止执行,向 Claude 返回错误信息
实战用例二:文件修改自动备份
每次 AI 写文件之前,先备份一份:
~/.claude/hooks/pre-write.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
if [ -f "$FILE_PATH" ]; then
BACKUP_DIR="$HOME/.claude/backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# 保留目录结构
RELATIVE_PATH=$(echo "$FILE_PATH" | sed 's|/|_|g')
cp "$FILE_PATH" "$BACKUP_DIR/${RELATIVE_PATH}.bak"
echo "✅ 已备份: $FILE_PATH → $BACKUP_DIR" >&2
fi
exit 0
这样即使 AI 写坏了文件,~/.claude/backups/ 里永远有当天的版本。
实战用例三:操作日志
记录 Claude Code 所有工具调用,方便事后审计:
~/.claude/hooks/audit-log.sh:
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
LOG_FILE="$HOME/.claude/audit.log"
# 提取关键参数(避免记录完整文件内容)
case "$TOOL_NAME" in
"Bash")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.command // ""' | head -c 200)
;;
"Write"|"Edit")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
;;
"Read"|"Glob"|"Grep")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.pattern // ""')
;;
*)
DETAIL=$(echo "$INPUT" | jq -r '.tool_input | to_entries[0].value // ""' | head -c 100)
;;
esac
echo "$TIMESTAMP | $TOOL_NAME | $DETAIL" >> "$LOG_FILE"
exit 0
配置到所有工具的 PostToolUse:
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/audit-log.sh" }]
}
]
}
}
一天下来,audit.log 会记录 Claude 做的每一件事。
实战用例四:飞书完成通知
长任务结束时推送通知,不用盯着屏幕等:
~/.claude/hooks/notify-done.sh:
#!/bin/bash
INPUT=$(cat)
# Stop hook 触发:Claude 完成了一轮响应
# 从环境变量读取飞书 webhook
if [ -z "$FEISHU_WEBHOOK" ]; then exit 0; fi
curl -s -X POST "$FEISHU_WEBHOOK" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": { "text": "✅ Claude Code 任务完成" }
}' > /dev/null
exit 0
在 settings.json 的 Stop hook 里注册,长编译或大规模重构完成后,手机飞书就收到通知。
组合:完整的安全 + 可观测配置
把上面的 hooks 组合在一起:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-bash.sh" }]
},
{
"matcher": "Write",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-write.sh" }]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/audit-log.sh" }]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
这套配置实现了:
- 🛡 危险命令自动拦截
- 💾 文件修改前自动备份
- 📋 所有操作完整审计日志
- 📱 任务完成飞书通知
Hook 脚本的几个设计原则
1. 保持快速
每个工具调用都会触发 Hook,慢 Hook 会明显拖慢 Claude Code 的响应速度。避免在 Hook 里做网络请求(除非是非阻塞的 Stop Hook)。
2. 静默失败
Hook 脚本里的错误不应该崩溃 Claude Code。在脚本里加 set +e,确保即使 Hook 出错,也不影响主流程。
3. 用 stderr 输出诊断信息
Hook 的 stdout 会传给 Claude,stderr 才是给用户看的日志。诊断信息一律写 stderr:
echo "debug: processing $FILE_PATH" >&2 # 正确
echo "debug: processing $FILE_PATH" # 会被 Claude 读到
4. 幂等设计
同一操作被触发多次,效果应该相同。备份脚本里用时间戳命名,避免覆盖。
与记忆系统结合
Hooks 和记忆架构可以结合使用。
比如,在 PostToolUse 里自动更新 Agent 的操作日志到 logs/:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
DETAIL=$(echo "$INPUT" | jq -r '.tool_input | tostring' | head -c 100)
DAILY_LOG="./memory/logs/$(date +%Y-%m-%d)_ops.md"
echo "- $(date +%H:%M) [$TOOL] $DETAIL" >> "$DAILY_LOG"
exit 0
这样 Agent 的每次操作都自动沉淀进记忆,下次会话能复盘”上次到底做了什么”。
Hooks 系统把 Claude Code 从”黑盒 AI”变成了”可观测、可审计、可干预的工程工具”。
从信任,到验证——这才是在生产环境放心使用 AI 的正确姿势。
系列文章:Claude Code 实战 · 相关阅读:Claude Code 的记忆架构 · MCP 协议实战
Most people use Claude Code like this: give an instruction, watch the result, undo if something went wrong.
That works. But it has a blind spot: you don’t know what the AI did until it’s already done it.
Claude Code’s Hooks system solves this. It lets you inject custom logic before and after every AI action — not just “allow or deny,” but a fully programmable interception layer.
What Hooks Are
Hooks are shell commands that Claude Code automatically executes when specific events occur.
Four hook events are currently supported:
| Hook | Trigger |
|---|---|
PreToolUse | Before AI calls any tool |
PostToolUse | After AI calls any tool |
Notification | When Claude Code emits a notification |
Stop | When Claude Code finishes responding |
Each hook can:
- Receive the full context of the tool call (tool name, parameters, results)
- Execute arbitrary shell commands
- Influence tool call behavior (PreToolUse can block execution)
Configuration
Hooks are configured in ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/pre-bash.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/post-write.sh"
}
]
}
]
}
}
matcher accepts tool names (Bash, Write, Edit, Read, etc.) or * to match all tools.
Example 1: Blocking Dangerous Commands
The most common use case: prevent AI from executing high-risk operations.
~/.claude/hooks/pre-bash.sh:
#!/bin/bash
# Read the tool call JSON input
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# Intercept dangerous patterns
DANGEROUS_PATTERNS=(
"rm -rf /"
"git push --force"
"DROP TABLE"
"chmod 777"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "Hook blocked: detected dangerous pattern '$pattern'" >&2
# Exit code 2 = block tool execution and return error
exit 2
fi
done
exit 0
Hook exit codes determine behavior:
0— allow execution1— log a warning but allow execution2— block execution, return error message to Claude
Example 2: Auto-Backup Before File Writes
Before AI writes to any file, back up the original:
~/.claude/hooks/pre-write.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
if [ -f "$FILE_PATH" ]; then
BACKUP_DIR="$HOME/.claude/backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Preserve directory structure in filename
RELATIVE_PATH=$(echo "$FILE_PATH" | sed 's|/|_|g')
cp "$FILE_PATH" "$BACKUP_DIR/${RELATIVE_PATH}.bak"
echo "Backed up: $FILE_PATH -> $BACKUP_DIR" >&2
fi
exit 0
Even if AI corrupts a file, ~/.claude/backups/ always has that day’s version.
Example 3: Operation Audit Log
Record every Claude Code tool call for post-hoc review:
~/.claude/hooks/audit-log.sh:
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
LOG_FILE="$HOME/.claude/audit.log"
# Extract key parameters (avoid logging full file contents)
case "$TOOL_NAME" in
"Bash")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.command // ""' | head -c 200)
;;
"Write"|"Edit")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
;;
"Read"|"Glob"|"Grep")
DETAIL=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.pattern // ""')
;;
*)
DETAIL=$(echo "$INPUT" | jq -r '.tool_input | to_entries[0].value // ""' | head -c 100)
;;
esac
echo "$TIMESTAMP | $TOOL_NAME | $DETAIL" >> "$LOG_FILE"
exit 0
Register this for all tools in PostToolUse:
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/audit-log.sh" }]
}
]
}
}
By end of day, audit.log has a complete record of everything Claude did.
Example 4: Task Completion Notification
Push a notification when a long task finishes — no need to sit watching the screen:
~/.claude/hooks/notify-done.sh:
#!/bin/bash
INPUT=$(cat)
# Stop hook fires: Claude has finished one round of response
# Read webhook URL from environment variable
if [ -z "$FEISHU_WEBHOOK" ]; then exit 0; fi
curl -s -X POST "$FEISHU_WEBHOOK" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": { "text": "Claude Code task complete" }
}' > /dev/null
exit 0
Register this in the Stop hook. After a long compile or large-scale refactor, your phone gets a notification.
Combined: A Full Safety + Observability Config
Put everything together:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-bash.sh" }]
},
{
"matcher": "Write",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-write.sh" }]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/audit-log.sh" }]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
This configuration gives you:
- Automatic blocking of dangerous commands
- Auto-backup before every file write
- Complete audit log of all operations
- Task completion push notifications
Hook Design Principles
1. Keep it fast
Every tool call triggers hooks. Slow hooks visibly degrade Claude Code’s response speed. Avoid network requests in hooks (exception: non-blocking Stop hooks).
2. Fail silently
Errors in hook scripts shouldn’t crash Claude Code. Add set +e to your scripts so that even if a hook fails, the main flow is unaffected.
3. Use stderr for diagnostic output
A hook’s stdout gets sent to Claude. stderr is for the user. All diagnostic messages go to stderr:
echo "debug: processing $FILE_PATH" >&2 # correct
echo "debug: processing $FILE_PATH" # Claude will read this
4. Design for idempotency
The same operation triggered multiple times should produce the same result. In backup scripts, use timestamps in filenames to avoid overwriting.
Integrating with the Memory System
Hooks and the memory architecture work well together.
For example, automatically append agent operations to logs/ in PostToolUse:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
DETAIL=$(echo "$INPUT" | jq -r '.tool_input | tostring' | head -c 100)
DAILY_LOG="./memory/logs/$(date +%Y-%m-%d)_ops.md"
echo "- $(date +%H:%M) [$TOOL] $DETAIL" >> "$DAILY_LOG"
exit 0
Every agent action automatically flows into memory, so the next session can review “what exactly happened last time.”
Hooks transform Claude Code from a “black box AI” into an observable, auditable, interruptible engineering tool.
From trusting — to verifying. That’s the right posture for using AI confidently in production.
Series: Claude Code in Practice · Related: Claude Code Memory Architecture · MCP in Practice
相关文章
Claude Code 15 个实用技巧:从够用到真正好用
大多数人只用到了 Claude Code 20% 的能力。这 15 个技巧覆盖从上下文管理、工作流加速到防坑实践——每一条都来自真实使用中的摸索。
Claude Code 的记忆架构:如何让 AI 真正记住你的项目
大多数人用 Claude Code 的方式是错的——每次对话都从零开始,反复解释背景。本文分享我在真实多 Agent 系统中摸索出的五层记忆架构,让 AI 持续积累项目知识。
MCP 协议实战:给你的 AI 装上真正的手和眼
MCP(Model Context Protocol)让 AI 能够调用真实工具、读取实时数据。本文从原理到实战,带你真正用起来——不只是理解概念,而是搭出一个能工作的 MCP 服务器。
这篇文章对你有帮助吗?
分享这篇文章
引用此文
讨论
这篇文章让你感觉
喜欢这篇文章?
订阅 RSS,第一时间收到新文章推送
私人笔记
仅保存在本地浏览器讨论
评论加载中...