Claude Code 的 Hooks、Slash Command 与自动化
前十一篇文章,主要靠人工操作------输入指令、看输出、确认、提交。这对于个人项目够了,但团队协作和日常高频使用下,手动流程会让你慢慢开始不耐烦。
Claude Code 有一套事件系统(Hooks)和自定义命令(Slash Command),可以把你每次重复的操作自动化。这篇文章从三个实际场景出发,把配置逻辑讲清楚。
Hooks 是什么
Claude Code 在执行过程中会触发一些生命周期事件。你可以在这些事件上挂载脚本------事件触发时,Claude Code 自动跑你的脚本。
八个事件:
| 事件 | 触发时机 | 能拦截吗 |
|---|---|---|
PreToolUse |
工具调用之前 | 能,exit code 2 阻止执行 |
PostToolUse |
工具调用之后 | 能,exit code 2 阻止后续 |
UserPromptSubmit |
用户提交 prompt | 能 |
SessionStart |
会话启动 | 不能 |
Stop |
会话结束前 | 能 |
SubagentStop |
子 Agent 结束 | 不能 |
Notification |
通知事件 | 不能 |
PreCompact |
上下文压缩前 | 不能 |
配置写在 .claude/settings.json 的 hooks 字段里。
场景一:每次 AI 写代码后自动格式化
AI 写完代码之后,缩进和空行有时候不够干净。每次手动跑 prettier 很烦。
配一个 PostToolUse 钩子------当 Write 或 Edit 工具执行完后,自动对修改的文件跑格式化。创建一个脚本 .claude/hooks/format.sh:
bash
#!/bin/bash
# 从 stdin 读取 Claude Code 传入的 JSON
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))")
if [ -n "$FILE_PATH" ]; then
npx prettier --write "$FILE_PATH" 2>/dev/null || true
fi
然后在 settings.json 里挂上:
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/format.sh"
}
]
}
]
}
}
解释一下:
matcher: "Write|Edit"--- 只在文件写入或编辑时触发,Bash、Grep 等操作不触发- Hook 脚本从 stdin 读 JSON,其中
tool_input.file_path是当前操作的文件路径 2>/dev/null || true--- prettier 不支持的文件类型会报错,吞掉错误不中断流程
效果:AI 写完代码 → 自动格式化 → 你看到的 diff 已经是格式化后的版本。
场景二:危险命令拦一道
AI 在 Bash 里跑命令,理论上不会故意破坏,但你也不知道它会不会在你的项目里 rm -rf。PreToolUse 可以在命令执行前拦截:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/block-dangerous.py"
}
]
}
]
}
}
配套的 Python 脚本 .claude/hooks/block-dangerous.py:
python
import json, sys, os
TOOL_INPUT = json.loads(sys.stdin.read())
command = TOOL_INPUT.get("tool_input", {}).get("command", "")
DANGEROUS_PATTERNS = [
"rm -rf /",
"git push --force",
"git reset --hard",
"sudo ",
"chmod 777",
"DROP TABLE",
"DROP DATABASE",
]
for pattern in DANGEROUS_PATTERNS:
if pattern.lower() in command.lower():
print(f"\n BLOCKED: Dangerous command detected: '{pattern}'")
print(f" Full command: {command}")
print(" To proceed, rephrase the command or manually approve.")
sys.exit(2) # exit code 2 = block
sys.exit(0) # exit code 0 = allow
脚本从 stdin 读取 JSON 格式的工具调用信息,检查 command 是否匹配危险模式。匹配到就 exit code 2,Claude Code 阻止执行并在终端显示 BLOCKED 消息。
你加到 DANGEROUS_PATTERNS 里的规则会跟着项目走(.claude/hooks/ 提交到 git),整个团队共享。
场景三:Slash Command------封装常用流程
Hooks 负责"自动触发",Slash Command 负责"手动唤起"。你在 Claude Code 里输入 /project:commit,它会执行对应的指令。
定义方式:在 .claude/commands/ 下创建 Markdown 文件。
.claude/commands/commit.md --- 生成 commit message 并提交:
markdown
---
description: 分析变更并生成 Conventional Commits 格式的 commit message
---
请执行以下步骤:
1. 运行 `git diff --staged` 和 `git log --oneline -5` 查看待提交的变更和最近的提交历史
2. 根据变更内容生成 commit message:
- 遵循 Conventional Commits 格式
- 标题不超过 72 字符
- 用英文写 message
3. 把生成的 message 展示给我审查,等我确认后再执行 `git commit -m "..."`
使用方式------在 Claude Code 里输入:
/project:commit
Claude Code 读取 commit.md,把 YAML 头之后的正文作为指令执行。生成 message、展示、等你确认、提交。
.claude/commands/pr.md --- 创建 PR:
markdown
---
description: 分析当前分支变更并创建 Pull Request
---
请执行以下步骤:
1. 检查当前分支名和远程状态
2. 运行 `git diff main...HEAD --stat` 查看变更文件
3. 生成 PR 标题和描述:
- 标题:简洁描述变更(中文)
- 描述:列出改动点、测试情况、截图位置
4. 展示给我审查,确认后用 `gh pr create` 创建
写完一个功能后,/project:pr 一键搞定 PR。
场景四:启动会话时自动加载项目环境
SessionStart 钩子在 Claude Code 每次启动时运行。适合加载环境变量、检查依赖、激活虚拟环境。
json
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "source .claude/hooks/setup-env.sh"
}
]
}
]
}
}
.claude/hooks/setup-env.sh:
bash
#!/bin/bash
# 激活 Python 虚拟环境
if [ -f .venv/bin/activate ]; then
source .venv/bin/activate
fi
# 检查必要依赖
if ! command -v pytest &> /dev/null; then
echo "⚠️ pytest not found --- tests won't run. Install: pip install pytest"
fi
# 设置环境变量
export PYTHONPATH="${PWD}/src:${PYTHONPATH}"
Claude Code 启动时自动激活 venv、检查依赖、设置 PYTHONPATH。不用每次手动 source .venv/bin/activate 了。
配置文件的最终结构
把这几个场景的配置整合到一起,项目里的完整配置:
项目根目录/
├── .claude/
│ ├── settings.json # hooks + permissions + 其他设置
│ ├── settings.local.json # 个人覆盖配置(不提交 git)
│ ├── commands/
│ │ ├── commit.md # /commit
│ │ └── pr.md # /pr
│ └── hooks/
│ ├── block-dangerous.py # PreToolUse 拦截
│ ├── setup-env.sh # SessionStart 环境
│ └── format.sh # PostToolUse 格式化
├── .claudeignore # 上下文排除
├── CLAUDE.md # 项目说明书
└── CLAUDE.local.md # 个人覆盖指令
分层职责:
CLAUDE.md--- 告诉 AI 怎么写代码(代码风格、项目规则).claudeignore--- 告诉 AI 不要看什么(排除目录)settings.json--- 告诉 Claude Code 怎么执行(权限、Hooks)commands/--- 封装你常做的重复操作hooks/--- 在特定时机自动触发的脚本
踩到的坑
1. Hook 脚本的性能很重要
PostToolUse 在每次工具调用后触发。如果你的格式化脚本很慢(比如 eslint 扫描整个项目),会严重影响使用体验。只格式化当前文件,不做全项目检查。
2. matcher 匹配模式要精确
matcher: "Edit" 只匹配 Edit 工具,matcher: "Write|Edit" 匹配两者。matcher 是 glob/管道匹配,不是完整正则------用 | 分隔、* 通配。如果你写 matcher: "W*" 会匹配 Write、WebSearch、WebFetch。建议用完整工具名。
3. settings.local.json 和 settings.json 的区别
settings.json 提交到 git,团队共享。settings.local.json 不提交,放个人偏好。如果你不愿意团队所有人继承你的权限配置,写到 local 里。
4. Hook 脚本的 exit code 含义
- exit 0 = 允许,继续执行
- exit 1 = 非阻塞错误,显示 warning 但继续
- exit 2 = 阻止,Claude Code 中止操作
PreToolUse 用 exit 2 拦截,PostToolUse 用 exit 0 放行(格式化失败不应该阻止代码落地)。
下一篇
Hooks 和命令让 Claude Code 从交互式工具变成了可编程平台。下一篇更深入------Agent 模式和 Chat 模式到底有什么区别,什么时候该用哪一种。