Claude Code Hooks 入门:自动格式化 + 文件保护这样配

第四章. Hooks 自动化机制

本章摘要:每次让 Claude 改完代码,你都要手动运行 prettier 格式化?每次 Claude 要执行危险命令,你都担心漏看?Hooks 机制让你可以在 Claude 执行操作的 “前后” 自动触发脚本——格式化、校验、通知,全部自动化。

本章学习路径

阶段内容解锁能力
第一阶段Hooks 事件全景理解 Claude 生命周期中的所有可拦截点
第二阶段Hook 配置与编写掌握 Hook 的配置语法和输入输出格式
第三阶段实战案例实现自动格式化、文件保护、语音通知

4.1. Hooks 事件全景

上一章我们学会了用 MCP 扩展 Claude 的能力。但有个问题没解决:Claude 执行操作时,我们能不能 “插一脚”?

“Claude 每次改完 TypeScript 文件,我都要手动运行 prettier,太烦了。”

“Claude 要删除文件时,我想先备份一下,但它执行太快我来不及。”

钩子在特定事件发生时自动触发的脚本 机制就是为了解决这些问题。它让你可以在 Claude 执行操作的 之前 或 之后 自动运行自定义脚本。

4.1.1. 工具执行类事件

这类事件与 Claude 调用工具(如读写文件、执行命令)相关:

事件名触发时机典型用途
PreToolUse工具执行 之前拦截危险操作、修改参数
PostToolUse工具执行 之后自动格式化、日志记录
PermissionRequest弹出权限确认框时自动批准/拒绝特定操作

PreToolUse 的特殊能力

这个事件不仅能 “观察”,还能 “干预”。你的脚本可以:

  • 返回 allow:自动批准,跳过用户确认
  • 返回 deny:直接拒绝,阻止执行
  • 返回 ask:弹出确认框,让用户决定

4.1.2. 会话生命周期事件

这类事件与 Claude Code 会话的开始、结束相关:

事件名触发时机典型用途
SessionStart会话开始时加载环境变量、安装依赖
SessionEnd会话结束时清理临时文件、保存状态
StopClaude 完成响应时检查任务是否真正完成
SubagentStop子代理完成任务时验证子代理输出质量

SessionStart 的特殊能力

这个事件可以向会话注入环境变量。你的脚本可以写入 $CLAUDE_ENV_FILE 文件,里面的环境变量会在后续所有 Bash 命令中生效。

4.1.3. 其他事件

事件名触发时机典型用途
NotificationClaude 发送通知时自定义通知方式(桌面弹窗、语音提醒)
UserPromptSubmit用户提交提示词时校验输入、添加上下文
PreCompact执行压缩操作前保存重要上下文

4.1.4. 本节小结

Hooks 事件的分类:

类别事件核心能力
工具执行类PreToolUse, PostToolUse, PermissionRequest拦截、修改、记录工具调用
会话生命周期SessionStart, SessionEnd, Stop, SubagentStop初始化、清理、验证
其他Notification, UserPromptSubmit, PreCompact通知、校验、压缩

4.2. Hook 配置与编写

知道了有哪些事件可以拦截,现在来看看如何配置和编写 Hook。

4.2.1. 配置文件结构

Hooks 配置在 settings.json 中,基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"hooks": {
"事件名": [
{
"matcher": "匹配模式",
"hooks": [
{
"type": "command",
"command": "要执行的命令",
"timeout": 60
}
]
}
]
}
}

字段说明

字段作用示例
matcher匹配工具名(支持正则)"Write", "Edit|Write", "Bash"
typeHook 类型"command"(执行命令)或 "prompt"(LLM 评估)
command要执行的 Shell 命令"prettier --write \"$file\""
timeout超时时间(秒)60

4.2.2. matcher 匹配规则

matcher 决定了 Hook 在什么情况下触发:

模式含义示例
精确匹配只匹配指定工具"Write" 只匹配 Write 工具
正则匹配匹配多个工具"Edit|Write" 匹配 Edit 或 Write
通配符匹配所有工具"*"""

常见工具名

  • Bash —— 执行 Shell 命令
  • Read —— 读取文件
  • Write —— 创建/覆盖文件
  • Edit —— 修改文件
  • Glob —— 文件模式匹配
  • Grep —— 内容搜索

4.2.3. 输入 JSON 结构

Hook 脚本通过 stdin 接收 JSON 格式的输入数据。不同事件的输入结构略有不同。

PreToolUse / PostToolUse 输入示例

1
2
3
4
5
6
7
8
9
10
11
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.ts",
"content": "文件内容..."
},
"cwd": "/project/root",
"permission_mode": "default"
}

Notification 输入示例

1
2
3
4
5
6
{
"session_id": "abc123",
"hook_event_name": "Notification",
"message": "Claude 需要你的确认",
"notification_type": "permission_prompt"
}

4.2.4. 输出与决策控制

Hook 脚本通过 退出码stdout 来控制 Claude 的行为。

退出码含义

退出码含义效果
0成功继续执行,stdout 内容可选择性展示
2阻断阻止操作,stderr 内容反馈给 Claude
其他非阻断错误继续执行,stderr 内容记录到日志

JSON 输出格式(高级控制)

对于 PreToolUse 事件,你可以输出 JSON 来精细控制:

1
2
3
4
5
6
7
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "自动批准文档文件读取"
}
}

permissionDecision 可选值:

  • "allow" —— 自动批准
  • "deny" —— 拒绝执行
  • "ask" —— 弹出确认框

4.2.5. 本节小结

Hook 配置的核心要素:

要素作用关键点
matcher决定何时触发支持正则表达式
command决定执行什么通过 stdin 接收 JSON
退出码决定是否阻断2 表示阻断
stdout决定如何控制可输出 JSON 精细控制

4.3 实战案例

4.3.1. 桌面通知 + 语音提醒(Notification)

痛点:Claude 在后台干活时,你切到别的窗口了。它需要你确认权限,但你根本没注意到。等你回来一看,它已经等了 10 分钟。

方案:当 Claude 需要你输入时,自动弹出桌面通知 + 语音提醒。

macOS 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 需要你的输入\" with title \"Claude Code\" sound name \"Glass\"' && say '主人,Claude 在等你'"
}
]
}
]
}
}

Linux 配置

1
2
# 先安装依赖
sudo apt install libnotify-bin espeak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude 需要你的输入' && espeak '主人,Claude 在等你'"
}
]
}
]
}
}

Windows 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Notification": [
{
"hooks": [
{
"command": "powershell -WindowStyle Hidden -Command \"(New-Object -ComObject WScript.Shell).Popup('Claude 需要你的输入', 0, 'Claude Code', 4096)\"",
"type": "command"
}
],
"matcher": ""
}
]
},
}

验证方式:启动 Claude Code,让它执行一个需要权限确认的操作(如删除文件),观察是否收到通知。


4.3.2. 命令执行日志(PreToolUse)

痛点:Claude 执行了一堆命令,事后你想回顾一下它到底干了啥,但终端历史已经被冲掉了。

方案:把 Claude 执行的每一条 Bash 命令都记录到日志文件。

步骤 1:创建日志脚本

创建文件 ~/.claude/hooks/log-command.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

# 读取 stdin 中的 JSON,提取命令内容
read -r INPUT
COMMAND=$(echo "$INPUT" | grep -o '"command":"[^"]*"' | cut -d'"' -f4)

# 如果提取到了命令,就记录到日志
if [ -n "$COMMAND" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $COMMAND" >> ~/.claude/command-history.log
fi

# 返回 0,不阻断执行
exit 0

步骤 2:设置执行权限

1
chmod +x ~/.claude/hooks/log-command.sh

步骤 3:配置 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/log-command.sh"
}
]
}
]
}
}

验证方式:让 Claude 执行几条命令,然后查看 ~/.claude/command-history.log


4.3.3. 自动格式化代码(PostToolUse)

痛点:Claude 改完代码后,格式经常不符合项目规范。你每次都要手动跑 prettiereslint --fix

方案:Claude 修改文件后,自动运行格式化工具。

步骤 1:创建格式化脚本

创建文件 ~/.claude/hooks/auto-format.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash

# 读取 stdin 中的 JSON,提取文件路径
read -r INPUT
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":"[^"]*"' | cut -d'"' -f4)

# 如果没有文件路径,直接退出
if [ -z "$FILE_PATH" ]; then
exit 0
fi

# 根据文件扩展名选择格式化工具
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.css|*.scss|*.md)
# 检查 prettier 是否存在
if command -v npx &> /dev/null; then
npx prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
*.py)
# 检查 black 是否存在
if command -v black &> /dev/null; then
black "$FILE_PATH" 2>/dev/null
fi
;;
*.go)
# 检查 gofmt 是否存在
if command -v gofmt &> /dev/null; then
gofmt -w "$FILE_PATH" 2>/dev/null
fi
;;
esac

exit 0

步骤 2:设置执行权限

1
chmod +x ~/.claude/hooks/auto-format.sh

步骤 3:配置 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/auto-format.sh"
}
]
}
]
}
}

验证方式:让 Claude 创建一个格式混乱的 .ts 文件,观察保存后是否被自动格式化。


4.3.4. 文件修改前自动备份(PreToolUse)

痛点:Claude 改坏了一个文件,你想回滚,但 Git 还没提交,改动已经丢了。

方案:在 Claude 修改任何文件之前,自动备份原文件。

步骤 1:创建备份脚本

创建文件 ~/.claude/hooks/auto-backup.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

# 读取 stdin 中的 JSON,提取文件路径
read -r INPUT
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":"[^"]*"' | cut -d'"' -f4)

# 如果文件不存在(新建文件),不需要备份
if [ ! -f "$FILE_PATH" ]; then
exit 0
fi

# 创建备份目录
BACKUP_DIR=~/.claude/backups/$(date '+%Y-%m-%d')
mkdir -p "$BACKUP_DIR"

# 生成备份文件名(原文件名 + 时间戳)
FILENAME=$(basename "$FILE_PATH")
BACKUP_FILE="$BACKUP_FILE="$BACKUP_DIR/${FILENAME}.$(date '+%H%M%S').bak"

# 执行备份
cp "$FILE_PATH" "$BACKUP_FILE"

exit 0

步骤 2:设置执行权限

1
chmod +x ~/.claude/hooks/auto-backup.sh

步骤 3:配置 Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/auto-backup.sh"
}
]
}
]
}
}

验证方式:让 Claude 修改一个已有文件,然后检查 ~/.claude/backups/ 目录下是否生成了备份。

清理策略:备份会越积越多,建议定期清理。可以加一个 cron 任务,删除 7 天前的备份:

1
2
# 添加到 crontab -e
0 3 * * * find ~/.claude/backups -type f -mtime +7 -delete

4.3.5. 会话启动时自动激活环境(SessionStart)

痛点:你的项目用了 Python 虚拟环境或 Node 版本管理器(nvm)。每次启动 Claude Code,它执行命令时都用的是系统默认环境,而不是项目环境。

方案:在会话启动时,自动激活项目所需的环境。

Python 虚拟环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "echo 'source /path/to/your/project/venv/bin/activate' >> \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}

Node 版本切换配置(nvm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "echo 'export NVM_DIR=\"$HOME/.nvm\" && [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" && nvm use 18' >> \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}

原理说明$CLAUDE_ENV_FILE 是 Claude Code 提供的特殊环境变量,指向一个临时文件。你往这个文件里写入的内容,会在后续所有 Bash 命令执行前被 source。

验证方式:启动 Claude Code,让它执行 which pythonnode -v,观察输出是否是你期望的环境。


4.3.6. 本节小结

四个实战案例的对比:

案例事件解决的痛点核心逻辑
桌面通知 + 语音NotificationClaude 等你输入时你没注意到调用系统通知 API
命令执行日志PreToolUse事后想回顾 Claude 执行了什么提取命令写入日志文件
自动格式化PostToolUse每次都要手动跑 prettier根据文件类型调用格式化工具
自动备份PreToolUseClaude 改坏文件后无法回滚修改前复制原文件
自动激活环境SessionStartClaude 用的不是项目环境写入环境变量到 CLAUDE_ENV_FILE

4.4. 本章总结与 Hooks 速查

本章我们学习了 Hooks 自动化机制——在 Claude 执行操作的前后自动触发脚本。核心思想是:把重复性的人工操作交给脚本,把人类的注意力留给真正需要判断的事情

4.4.1. 事件速查表

事件触发时机能做什么
PreToolUse工具执行前拦截、修改、记录
PostToolUse工具执行后格式化、校验、通知
SessionStart会话开始初始化环境、加载配置
SessionEnd会话结束清理临时文件
Notification需要用户注意时自定义通知方式

4.4.2. 场景速查:遇到这些情况,直接用

场景 1:桌面通知 + 语音提醒(macOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude 需要你的输入\" with title \"Claude Code\" sound name \"Glass\"' && say '主人,Claude 在等你'"
}
]
}
]
}
}

场景 2:记录所有 Bash 命令到日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/log-command.sh"
}
]
}
]
}
}

场景 3:修改文件前自动备份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/auto-backup.sh"
}
]
}
]
}
}

场景 4:修改代码后自动格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/auto-format.sh"
}
]
}
]
}
}

场景 5:会话开始时自动激活虚拟环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "echo 'source /path/to/venv/bin/activate' >> \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}