Hooks 实战案例
Hooks Practical Examples
本章展示常见的 Hooks 配置案例,你可以直接复制到自己的项目中使用。
案例一:自动格式化代码
每当 Claude 创建或修改文件时,自动运行 Prettier 格式化。
配置
在 .claude/settings.json 中添加:
json
{
"hooks": {
"PostToolUse": [
{
"tool": "Write",
"handler": {
"type": "command",
"command": "bash -c 'INPUT=$(cat); FILE=$(echo $INPUT | jq -r .tool_params.file_path); if [[ \"$FILE\" =~ \\.(ts|tsx|js|jsx|css|json|md)$ ]]; then npx prettier --write \"$FILE\" 2>/dev/null; fi'"
}
},
{
"tool": "Edit",
"handler": {
"type": "command",
"command": "bash -c 'INPUT=$(cat); FILE=$(echo $INPUT | jq -r .tool_params.file_path); if [[ \"$FILE\" =~ \\.(ts|tsx|js|jsx|css|json|md)$ ]]; then npx prettier --write \"$FILE\" 2>/dev/null; fi'"
}
}
]
}
}更优雅的方式:使用脚本
创建 .claude/hooks/format.sh:
bash
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_params.file_path // empty')
if [ -z "$FILE" ]; then
exit 0
fi
# 检查文件扩展名
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx|*.css|*.json|*.md)
npx prettier --write "$FILE" 2>/dev/null
;;
*.py)
black "$FILE" 2>/dev/null
;;
*.go)
gofmt -w "$FILE" 2>/dev/null
;;
esac
exit 0配置引用脚本:
json
{
"hooks": {
"PostToolUse": [
{
"tool": "Write",
"handler": {
"type": "command",
"command": "bash .claude/hooks/format.sh"
}
}
]
}
}案例二:Bash 命令安全检查
阻止 Claude 执行危险的 Shell 命令。
脚本 .claude/hooks/bash-guard.js
javascript
const input = [];
process.stdin.on('data', (chunk) => input.push(chunk));
process.stdin.on('end', () => {
const data = JSON.parse(input.join(''));
const cmd = (data.tool_params?.command || '').toLowerCase();
// 危险命令模式
const blocked = [
{ pattern: /rm\s+-rf\s+\//, reason: '禁止删除根目录' },
{ pattern: /rm\s+-rf\s+~/, reason: '禁止删除用户目录' },
{ pattern: /git\s+push\s+.*--force/, reason: '禁止强制推送' },
{ pattern: /git\s+reset\s+--hard/, reason: '禁止硬重置' },
{ pattern: /drop\s+(table|database)/i, reason: '禁止删除数据库对象' },
{ pattern: />\s*\/dev\/sda/, reason: '禁止直接写入磁盘' },
{ pattern: /mkfs\./, reason: '禁止格式化磁盘' },
{ pattern: /chmod\s+777/, reason: '禁止设置不安全权限' },
{ pattern: /curl.*\|\s*(bash|sh)/, reason: '禁止管道执行远程脚本' },
];
for (const rule of blocked) {
if (rule.pattern.test(cmd)) {
console.log(JSON.stringify({
allow: false,
reason: rule.reason
}));
process.exit(1);
}
}
console.log(JSON.stringify({ allow: true }));
});配置
json
{
"hooks": {
"PreToolUse": [
{
"tool": "Bash",
"handler": {
"type": "command",
"command": "node .claude/hooks/bash-guard.js"
}
}
]
}
}案例三:自动运行测试
代码修改后,自动运行相关的测试文件。
脚本 .claude/hooks/auto-test.sh
bash
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_params.file_path // empty')
if [ -z "$FILE" ]; then
exit 0
fi
# 只处理源代码文件
case "$FILE" in
*.test.ts|*.test.tsx|*.spec.ts|*.spec.tsx)
# 修改的就是测试文件,直接运行
npx vitest run "$FILE" --reporter=verbose 2>&1 | tail -20
;;
src/*.ts|src/*.tsx)
# 查找对应的测试文件
TEST_FILE="${FILE%.ts}.test.ts"
TEST_FILE2="${FILE%.tsx}.test.tsx"
SPEC_FILE="${FILE%.ts}.spec.ts"
for TF in "$TEST_FILE" "$TEST_FILE2" "$SPEC_FILE"; do
if [ -f "$TF" ]; then
npx vitest run "$TF" --reporter=verbose 2>&1 | tail -20
break
fi
done
;;
esac
exit 0配置
json
{
"hooks": {
"PostToolUse": [
{
"tool": "Write",
"handler": {
"type": "command",
"command": "bash .claude/hooks/auto-test.sh",
"timeout": 60000
}
},
{
"tool": "Edit",
"handler": {
"type": "command",
"command": "bash .claude/hooks/auto-test.sh",
"timeout": 60000
}
}
]
}
}案例四:任务完成通知
Claude 完成任务后发送通知。
macOS 通知
json
{
"hooks": {
"Stop": [
{
"handler": {
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 任务已完成\" with title \"Claude Code\"'"
}
}
]
}
}Windows 通知(PowerShell)
json
{
"hooks": {
"Stop": [
{
"handler": {
"type": "command",
"command": "powershell -Command \"[System.Windows.Forms.MessageBox]::Show('Claude Code 任务已完成')\""
}
}
]
}
}Slack 通知
bash
#!/bin/bash
# .claude/hooks/notify-slack.sh
INPUT=$(cat)
EVENT=$(echo "$INPUT" | jq -r '.hook_event')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\": \"🤖 Claude Code: 任务已完成 (${EVENT})\"}"案例五:Git 提交前 Lint 检查
在 Claude 执行 git commit 前,自动运行 lint 检查。
脚本
bash
#!/bin/bash
# .claude/hooks/pre-commit-lint.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_params.command // empty')
# 检查是否是 git commit 命令
if echo "$COMMAND" | grep -q 'git commit'; then
# 运行 lint 检查
LINT_OUTPUT=$(npx eslint src/ --quiet 2>&1)
LINT_EXIT=$?
if [ $LINT_EXIT -ne 0 ]; then
echo "{\"allow\": false, \"reason\": \"Lint 检查未通过,请先修复以下问题:\n${LINT_OUTPUT}\"}"
exit 1
fi
fi
echo '{"allow": true}'配置
json
{
"hooks": {
"PreToolUse": [
{
"tool": "Bash",
"handler": {
"type": "command",
"command": "bash .claude/hooks/pre-commit-lint.sh"
}
}
]
}
}案例六:会话日志记录
记录每次会话的开始和结束信息。
脚本
bash
#!/bin/bash
# .claude/hooks/session-log.sh
INPUT=$(cat)
EVENT=$(echo "$INPUT" | jq -r '.hook_event')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
LOG_FILE=".claude/logs/sessions.log"
mkdir -p "$(dirname "$LOG_FILE")"
case "$EVENT" in
SessionStart)
echo "[$TIMESTAMP] SESSION_START $SESSION_ID" >> "$LOG_FILE"
;;
SessionEnd)
DURATION=$(echo "$INPUT" | jq -r '.duration_seconds // "unknown"')
COST=$(echo "$INPUT" | jq -r '.total_cost // "unknown"')
echo "[$TIMESTAMP] SESSION_END $SESSION_ID duration=${DURATION}s cost=\$${COST}" >> "$LOG_FILE"
;;
esac配置
json
{
"hooks": {
"SessionStart": [
{
"handler": {
"type": "command",
"command": "bash .claude/hooks/session-log.sh"
}
}
],
"SessionEnd": [
{
"handler": {
"type": "command",
"command": "bash .claude/hooks/session-log.sh"
}
}
]
}
}完整的生产配置示例
一个综合了多种 Hook 的完整配置:
json
{
"hooks": {
"PreToolUse": [
{
"tool": "Bash",
"handler": {
"type": "command",
"command": "node .claude/hooks/bash-guard.js"
}
}
],
"PostToolUse": [
{
"tool": "Write",
"handler": {
"type": "command",
"command": "bash .claude/hooks/format.sh"
}
},
{
"tool": "Edit",
"handler": {
"type": "command",
"command": "bash .claude/hooks/format.sh"
}
}
],
"Stop": [
{
"handler": {
"type": "command",
"command": "bash .claude/hooks/notify-slack.sh"
}
}
],
"SessionStart": [
{
"handler": {
"type": "command",
"command": "bash .claude/hooks/session-log.sh"
}
}
],
"SessionEnd": [
{
"handler": {
"type": "command",
"command": "bash .claude/hooks/session-log.sh"
}
}
]
}
}调试技巧
调试 Hook 时,可以在脚本中添加日志输出到文件:
bash
echo "$(date) - $INPUT" >> /tmp/claude-hooks-debug.log这样可以查看每次 Hook 接收到的完整输入数据。