Skip to content

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 接收到的完整输入数据。