Claude Code | | 约 22 分钟 | 8,618 字

Claude Code Hooks 系统详解

深入理解 Hooks 机制,在关键节点自动执行自定义逻辑

什么是 Hooks

我们在使用 Claude Code 的过程中,经常会想:能不能在它执行某个操作之前或之后,自动跑一段我们自己的逻辑?

比如:

  • 每次编辑文件后自动跑 lint
  • 执行 bash 命令前做安全检查
  • 任务完成后发一条通知到 Slack

这就是 Hooks 系统要解决的问题。Hooks 让我们可以在 Claude Code 的关键执行节点插入自定义脚本,实现工作流的自动化。


Hook 的类型

Claude Code 提供了以下几种 Hook 类型:

Hook 类型触发时机典型用途
PreToolUse工具执行之前参数校验、安全检查、权限控制
PostToolUse工具执行之后自动格式化、测试、日志记录
NotificationClaude Code 发送通知时自定义通知渠道(Slack、邮件等)
StopClaude Code 停止响应时清理工作、结果汇总

PreToolUse

在工具执行之前触发。我们可以用它来:

  • 拦截危险操作
  • 修改工具参数
  • 记录操作日志
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": {
          "type": "command",
          "command": "python3 /path/to/validate-command.py"
        }
      }
    ]
  }
}

PostToolUse

在工具执行之后触发。这是最常用的 Hook 类型:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "npx eslint --fix $CLAUDE_FILE_PATH"
        }
      }
    ]
  }
}

Notification

当 Claude Code 需要通知用户时触发:

{
  "hooks": {
    "Notification": [
      {
        "hook": {
          "type": "command",
          "command": "terminal-notifier -message '$CLAUDE_NOTIFICATION' -title 'Claude Code'"
        }
      }
    ]
  }
}

Stop

当 Claude Code 完成任务停止响应时触发:

{
  "hooks": {
    "Stop": [
      {
        "hook": {
          "type": "command",
          "command": "bash /path/to/cleanup.sh"
        }
      }
    ]
  }
}

配置方式

Hooks 在 .claude/settings.json 中配置。我们来看完整的配置结构:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "工具名称的正则表达式",
        "hook": {
          "type": "command",
          "command": "要执行的命令",
          "timeout": 10000
        }
      }
    ],
    "PostToolUse": [],
    "Notification": [],
    "Stop": []
  }
}

配置字段说明

字段类型说明
matcherstring正则表达式,匹配工具名称(仅 PreToolUse 和 PostToolUse)
hook.typestring目前只支持 "command"
hook.commandstring要执行的 shell 命令
hook.timeoutnumber超时时间(毫秒),默认 10000

环境变量

Hook 脚本可以访问以下环境变量:

环境变量说明可用的 Hook 类型
CLAUDE_TOOL_NAME当前工具名称PreToolUse, PostToolUse
CLAUDE_TOOL_INPUT工具输入参数(JSON)PreToolUse, PostToolUse
CLAUDE_TOOL_OUTPUT工具输出结果(JSON)PostToolUse
CLAUDE_FILE_PATH操作的文件路径PreToolUse, PostToolUse
CLAUDE_NOTIFICATION通知消息内容Notification
CLAUDE_SESSION_ID当前会话 ID所有类型

实战示例

示例 1:编辑后自动 Lint

这是最常见的需求——每次 Claude Code 编辑文件后,自动运行 ESLint 修复格式问题:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
        }
      }
    ]
  }
}

注意最后的 || true——我们不希望 lint 失败阻塞 Claude Code 的工作流。

示例 2:编辑后自动运行测试

每次修改源代码后,自动运行相关的测试文件:

#!/bin/bash
# scripts/auto-test.sh
FILE_PATH="$CLAUDE_FILE_PATH"

# 只对 src 目录下的文件触发
if [[ "$FILE_PATH" != src/* ]]; then
  exit 0
fi

# 推断测试文件路径
TEST_FILE="${FILE_PATH/src\//tests\/}"
TEST_FILE="${TEST_FILE/.ts/.test.ts}"

if [ -f "$TEST_FILE" ]; then
  npx vitest run "$TEST_FILE" --reporter=verbose 2>&1
fi

配置:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "bash scripts/auto-test.sh",
          "timeout": 30000
        }
      }
    ]
  }
}

示例 3:Bash 命令安全检查

在执行 bash 命令之前,检查是否包含危险操作:

#!/bin/bash
# scripts/safety-check.sh
INPUT="$CLAUDE_TOOL_INPUT"

# 解析命令内容
COMMAND=$(echo "$INPUT" | jq -r '.command // empty')

# 危险命令黑名单
DANGEROUS_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "dd if="
  "> /dev/sda"
  "mkfs"
  ":(){:|:&};:"
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -q "$pattern"; then
    echo "BLOCKED: 检测到危险命令: $pattern"
    exit 1
  fi
done

exit 0

当 Hook 脚本返回非零退出码时,Claude Code 会阻止该工具的执行。

示例 4:任务完成通知到 Slack

#!/bin/bash
# scripts/notify-slack.sh
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
MESSAGE="$CLAUDE_NOTIFICATION"

curl -s -X POST "$WEBHOOK_URL" \
  -H 'Content-type: application/json' \
  -d "{\"text\": \"Claude Code: $MESSAGE\"}" \
  > /dev/null 2>&1
{
  "hooks": {
    "Notification": [
      {
        "hook": {
          "type": "command",
          "command": "bash scripts/notify-slack.sh"
        }
      }
    ]
  }
}

示例 5:自动格式化 + 类型检查组合

#!/bin/bash
# scripts/post-edit.sh
FILE="$CLAUDE_FILE_PATH"

# 只处理 TypeScript 文件
if [[ "$FILE" != *.ts && "$FILE" != *.tsx ]]; then
  exit 0
fi

# 1. Prettier 格式化
npx prettier --write "$FILE" 2>/dev/null

# 2. ESLint 修复
npx eslint --fix "$FILE" 2>/dev/null

# 3. 类型检查(只报告,不阻塞)
npx tsc --noEmit --pretty 2>&1 | head -20

exit 0

Matcher 的高级用法

matcher 字段支持正则表达式,我们可以灵活匹配不同的工具:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": {
          "type": "command",
          "command": "bash scripts/check-bash.sh"
        }
      },
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "bash scripts/check-write.sh"
        }
      },
      {
        "matcher": ".*",
        "hook": {
          "type": "command",
          "command": "bash scripts/log-all-tools.sh"
        }
      }
    ]
  }
}

常用的工具名称:

工具名称说明
Bash执行 shell 命令
Read读取文件
Write写入文件
Edit编辑文件
Glob文件搜索
Grep内容搜索
WebFetch网页抓取

调试 Hooks

Hook 不生效?这里有几个调试技巧:

1. 检查配置文件位置

确保 .claude/settings.json 在项目根目录下:

cat .claude/settings.json | jq '.hooks'

2. 添加日志输出

在 Hook 脚本中添加日志:

#!/bin/bash
# 记录所有 Hook 调用
echo "[$(date)] Tool: $CLAUDE_TOOL_NAME, File: $CLAUDE_FILE_PATH" >> /tmp/claude-hooks.log

3. 单独测试脚本

先手动设置环境变量,单独运行 Hook 脚本:

CLAUDE_TOOL_NAME="Write" \
CLAUDE_FILE_PATH="src/index.ts" \
bash scripts/post-edit.sh

4. 检查超时设置

如果 Hook 脚本执行时间较长,记得调大 timeout:

{
  "hook": {
    "type": "command",
    "command": "bash scripts/slow-check.sh",
    "timeout": 60000
  }
}

完整配置示例

这是一个生产级的 Hooks 配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": {
          "type": "command",
          "command": "bash .claude/hooks/safety-check.sh",
          "timeout": 5000
        }
      },
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "bash .claude/hooks/pre-write-check.sh",
          "timeout": 5000
        }
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hook": {
          "type": "command",
          "command": "bash .claude/hooks/post-edit.sh",
          "timeout": 30000
        }
      }
    ],
    "Notification": [
      {
        "hook": {
          "type": "command",
          "command": "bash .claude/hooks/notify.sh"
        }
      }
    ],
    "Stop": [
      {
        "hook": {
          "type": "command",
          "command": "bash .claude/hooks/on-stop.sh"
        }
      }
    ]
  }
}

建议的目录结构:

.claude/
├── settings.json
└── hooks/
    ├── safety-check.sh
    ├── pre-write-check.sh
    ├── post-edit.sh
    ├── notify.sh
    └── on-stop.sh

最佳实践

1. 保持 Hook 脚本轻量

Hook 会在每次工具调用时执行,如果脚本太慢会严重影响体验:

  • 控制在 5 秒以内
  • 避免网络请求(除非是通知类 Hook)
  • 使用缓存减少重复计算

2. 优雅处理失败

#!/bin/bash
# 不要让 Hook 失败影响正常工作流
set +e  # 不因错误退出

# 你的逻辑
npx eslint --fix "$CLAUDE_FILE_PATH" 2>/dev/null

# 总是返回成功(除非你确实想阻塞操作)
exit 0

3. 按文件类型过滤

不要对所有文件都执行 Hook,按需过滤:

FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
  *.ts|*.tsx) run_typescript_checks ;;
  *.py)       run_python_checks ;;
  *.go)       run_go_checks ;;
  *)          exit 0 ;;
esac

4. 团队共享 Hook 配置

把 Hook 脚本放在版本控制中,团队成员可以共享:

# .gitignore 中不要忽略 .claude/hooks/
# 但要忽略个人配置
.claude/settings.local.json

5. PreToolUse 慎用阻塞

PreToolUse 返回非零退出码会阻止工具执行。只在真正需要安全检查时使用阻塞,其他场景用 PostToolUse 做事后处理。


常见问题

Q: Hook 脚本的工作目录是什么?

A: 工作目录是 Claude Code 的当前工作目录,通常是项目根目录。

Q: 多个 Hook 的执行顺序是什么?

A: 按照配置数组中的顺序依次执行。如果某个 PreToolUse Hook 返回非零退出码,后续 Hook 不会执行,工具调用也会被阻止。

Q: Hook 可以修改工具的输入参数吗?

A: PreToolUse Hook 可以通过 stdout 输出 JSON 来修改工具参数。如果 Hook 没有输出,则使用原始参数。

Q: 如何在 Hook 中区分不同的文件类型?

A: 通过 $CLAUDE_FILE_PATH 环境变量获取文件路径,然后在脚本中判断扩展名。


Hooks 是 Claude Code 工程化的第一步。掌握了它,你就拥有了在 AI 工作流中注入自定义逻辑的能力——这是从”使用工具”到”驾驭工具”的关键跨越。

评论

加载中...

相关文章

分享:

评论

加载中...