Claude Code Hooks自动化:让AI自动执行你的规则

阅读时间:约 18 分钟 | 难度:入门级 | 前置要求:已完成 Claude Code 安装和基础使用,了解 .claude/settings.json 配置方式(见 05-custom-commands.md
作者:林宁
访问官网:weelinking.com

TL;DR

Hooks 是 Claude Code 的"自动化传感器"–在特定事件(工具调用前后、用户提交输入、会话开始结束等)发生时,自动执行你指定的脚本。与在 CLAUDE.md 中写提示词不同,Hooks 是确定性执行的,不存在 AI "忘记"的问题。只需在 .claude/settings.json 中声明 Hook 类型、匹配器和脚本路径,就能实现代码自动格式化、危险文件保护、Git 提交质量检查等工作流,一次配置全员生效。

你将学到什么

  • 理解 Hooks 的核心价值及其与手动提示词方式的区别
  • 掌握全部 6 种 Hook 类型的触发时机和典型用途
  • 5 分钟创建并验证第一个 PostToolUse Hook
  • 读懂 settings.json 中 hooks 字段的完整结构和 matcher 匹配规则
  • 理解 allow / deny / ask / message 四种决策输出
  • 动手实现自动代码格式化、文件保护、Git 提交检查三个实战场景
  • 掌握安全警告和最佳实践

什么是 Hooks?为什么需要它?

传感器类比

把 Claude Code 想象成一辆智能汽车。Hooks 就是车上的传感器系统:胎压传感器在气压异常时自动报警,碰撞传感器在撞击时自动弹出安全气囊。你不需要每次上车前叮嘱"记得检查胎压"–传感器会 100% 执行。

手动提示词 vs Hooks

对比维度 提示词 / CLAUDE.md Hooks
可靠性 AI 可能忘记执行 100% 确定性触发
一致性 每次可能不同 每次完全相同
自动化 需要 AI 主动遵循 事件驱动自动执行
团队协作 每人都要提醒 配置一次全员生效
适用场景 灵活建议、上下文说明 强制规则、自动化流水线

一句话总结:提示词是"建议",Hooks 是"规则"。


6 种 Hook 类型一览

💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定

Claude Code 提供以下 6 种 Hook 事件(外加 Stop),覆盖从会话启动到结束的完整生命周期:

Hook 类型 触发时机 典型用途 能否阻止后续操作
PreToolUse 工具调用前 权限校验、参数验证、危险命令拦截
PostToolUse 工具调用后 代码格式化、自动备份、质量检查
UserPromptSubmit 用户输入提交后、AI 处理前 提示词增强、敏感词过滤
Notification 通知发送时 桌面推送、日志记录
SessionStart 会话开始时 环境检查、依赖校验
SessionEnd 会话结束时 清理临时文件、保存状态
Stop AI 停止响应时 状态保存、会话记录

执行流程可以简化为:

# 完整生命周期
用户输入 --> [UserPromptSubmit] --> Claude 处理 --> 决定调用工具
  --> [PreToolUse] --> 执行工具 --> [PostToolUse] --> 返回结果

5 分钟快速开始:创建第一个 Hook

💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定

下面用一个 PostToolUse Hook 演示完整流程:每次 Claude 写入文件后,自动打印一条提示。

第一步:创建 Hook 脚本

在项目根目录创建 .claude/hooks/ 目录,然后新建 post-write-hello.py

#!/usr/bin/env python3
"""
PostToolUse Hook 示例
每次 Write 工具执行后打印提示
"""
import sys
import json

# 从 stdin 读取工具执行信息(JSON 格式)
try:
    input_data = json.loads(sys.stdin.read())
except Exception:
    sys.exit(0)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")

if tool_name == "Write":
    # stderr 输出会显示在 Claude Code 界面
    print(f"[Hook] 文件已保存: {file_path}", file=sys.stderr)

sys.exit(0)

macOS / Linux 用户需要添加执行权限:

chmod +x .claude/hooks/post-write-hello.py

第二步:配置 settings.json

创建或编辑 .claude/settings.json,写入以下内容:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/post-write-hello.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

第三步:验证触发

重启 Claude Code,然后输入:

# 在 Claude Code 对话中输入:
# "帮我创建一个 test.txt 文件,内容是 Hello Hooks"

预期看到类似输出:

# Claude 执行 Write 工具后,Hook 自动打印:
# [Hook] 文件已保存: /your/project/test.txt

如果没有看到输出,用以下命令手动验证脚本是否正常:

echo '{"tool_name":"Write","tool_input":{"file_path":"test.txt"}}' | python .claude/hooks/post-write-hello.py
# 预期输出到 stderr: [Hook] 文件已保存: test.txt

配置格式详解

hooks 字段结构

.claude/settings.jsonhooks 的完整结构如下:

{
  "hooks": {
    "<HookType>": [
      {
        "matcher": "<工具名匹配规则>",
        "hooks": [
          {
            "type": "command",
            "command": "<要执行的命令>",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

各字段说明:

字段 必填 说明
HookType PreToolUse / PostToolUse / UserPromptSubmit / Notification / SessionStart / SessionEnd / Stop
matcher 工具名匹配规则,支持 Write(精确)、Write|Edit(多选)、.*(全部)。仅 PreToolUse 和 PostToolUse 需要
type 固定为 "command"
command 要执行的 Shell 命令或脚本路径
timeout 超时秒数,默认 60。建议简单检查设 5-10,格式化设 30,完整测试设 120

matcher 匹配器示例

"matcher": "Write"          // 精确匹配 Write 工具
"matcher": "Write|Edit"     // 匹配 Write 或 Edit
"matcher": "Bash"           // 精确匹配 Bash 工具
"matcher": ".*"             // 匹配所有工具(慎用)

同一个 HookType 下可以配置多组 matcher,每组 matcher 下可以挂载多个脚本,按顺序依次执行。


Hook 决策输出:控制工具是否执行

PreToolUse Hook 可以通过 stdout 输出 JSON 决策来控制工具行为:

{"decision": "deny", "message": "禁止修改此文件"}

四种决策值及含义:

decision 值 含义 工具是否执行
"allow" 明确允许
"deny" 拒绝执行,Claude 收到错误提示
"ask" 暂停并询问用户是否继续 等待用户决定
"message" 仅显示消息,不影响执行
无输出 默认允许

PostToolUse 和其他 Hook 类型不返回决策(工具已经执行完毕),只能通过 stderr 输出日志或执行后处理任务。


实战:自动代码格式化

需求

每次 Claude 写入 .js / .ts / .py 等文件后,自动运行对应的格式化工具(Prettier / Black)。

Hook 脚本

创建 .claude/hooks/post-auto-format.py

#!/usr/bin/env python3
"""PostToolUse Hook - 保存代码后自动格式化"""
import sys
import json
import subprocess
from pathlib import Path

FORMATTERS = {
    ".js":  'npx prettier --write "{file}"',
    ".ts":  'npx prettier --write "{file}"',
    ".jsx": 'npx prettier --write "{file}"',
    ".tsx": 'npx prettier --write "{file}"',
    ".py":  'black "{file}"',
}

EXCLUDED = {"node_modules", "dist", "build", ".git", "__pycache__"}

try:
    data = json.loads(sys.stdin.read())
except Exception:
    sys.exit(0)

if data.get("tool_name") != "Write":
    sys.exit(0)

file_path = data.get("tool_input", {}).get("file_path", "")
path = Path(file_path)

# 跳过排除目录
if any(part in EXCLUDED for part in path.parts):
    sys.exit(0)

cmd_template = FORMATTERS.get(path.suffix)
if not cmd_template:
    sys.exit(0)

cmd = cmd_template.format(file=file_path)
try:
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
    if result.returncode == 0:
        print(f"[AutoFormat] {path.name}: 格式化成功", file=sys.stderr)
    else:
        print(f"[AutoFormat] {path.name}: 失败 - {result.stderr[:80]}", file=sys.stderr)
except subprocess.TimeoutExpired:
    print(f"[AutoFormat] {path.name}: 超时", file=sys.stderr)
except FileNotFoundError:
    print(f"[AutoFormat] 格式化工具未安装", file=sys.stderr)

配置

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/post-auto-format.py",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

验证效果:让 Claude 创建一个 .js 文件,观察是否出现 [AutoFormat] app.js: 格式化成功 提示。


实战:文件保护规则

需求

禁止 Claude 写入或编辑 production/ 目录下的任何文件。

Hook 脚本

创建 .claude/hooks/pre-protect-production.py

#!/usr/bin/env python3
"""PreToolUse Hook - 保护 production 目录"""
import sys
import json

try:
    data = json.loads(sys.stdin.read())
except Exception:
    sys.exit(0)

if data.get("tool_name") not in ("Write", "Edit"):
    sys.exit(0)

file_path = data.get("tool_input", {}).get("file_path", "").replace("\\", "/")

PROTECTED = ["production/", "prod/", ".env"]
for p in PROTECTED:
    if p in file_path:
        decision = {
            "decision": "deny",
            "message": f"禁止修改受保护路径: {file_path} (匹配规则: {p})"
        }
        print(json.dumps(decision, ensure_ascii=False))
        sys.exit(0)

sys.exit(0)

配置

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/pre-protect-production.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

当 Claude 尝试修改 production/config.json 时,会收到拒绝提示并停止操作。


实战:Git 提交质量检查

需求

在 Claude 执行 git commit 前,自动运行分支保护检查、敏感信息扫描和代码 lint。

Hook 脚本

创建 .claude/hooks/git-pre-commit-checker.py

#!/usr/bin/env python3
"""PreToolUse Hook - Git 提交前质量检查"""
import sys
import json
import subprocess
import re

CONFIG = {
    "protected_branches": ["main", "master", "production"],
    "secret_patterns": [
        r"(?i)(api[_-]?key|apikey)\s*[=:]\s*[\"']?[\w-]{20,}",
        r"sk-[a-zA-Z0-9]{20,}",
        r"ghp_[a-zA-Z0-9]{36,}",
    ],
}

def run_cmd(cmd):
    try:
        r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
        return r.returncode, r.stdout, r.stderr
    except Exception as e:
        return -1, "", str(e)

def check_branch():
    code, out, _ = run_cmd("git rev-parse --abbrev-ref HEAD")
    branch = out.strip()
    if branch in CONFIG["protected_branches"]:
        return False, f"禁止直接提交到受保护分支: {branch}"
    return True, f"当前分支: {branch}"

def check_secrets():
    code, out, _ = run_cmd("git diff --cached")
    for pattern in CONFIG["secret_patterns"]:
        if re.search(pattern, out):
            return False, f"发现可疑敏感信息 (匹配: {pattern[:30]}...)"
    return True, "敏感信息检查通过"

def check_lint():
    code, out, _ = run_cmd("git diff --cached --name-only --diff-filter=ACMR")
    py_files = [f for f in out.strip().split("\n") if f.endswith(".py")]
    if py_files:
        rc, stdout, stderr = run_cmd(f'ruff check {" ".join(py_files)}')
        if rc != 0:
            return False, f"Python lint 问题:\n{stdout or stderr}"
    return True, "代码风格检查通过"

try:
    data = json.loads(sys.stdin.read())
except Exception:
    sys.exit(0)

if data.get("tool_name") != "Bash" or "git commit" not in data.get("tool_input", {}).get("command", ""):
    sys.exit(0)

checks = [("分支检查", check_branch), ("敏感信息", check_secrets), ("代码风格", check_lint)]
all_passed = True

print("\n" + "=" * 50, file=sys.stderr)
print("Git 提交前检查报告", file=sys.stderr)
print("=" * 50, file=sys.stderr)

for name, fn in checks:
    passed, msg = fn()
    status = "PASS" if passed else "FAIL"
    print(f"  [{status}] {name}: {msg}", file=sys.stderr)
    if not passed:
        all_passed = False

print("=" * 50, file=sys.stderr)

if not all_passed:
    print(json.dumps({"decision": "ask", "message": "部分检查未通过,是否仍要继续提交?"}, ensure_ascii=False))

配置

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/git-pre-commit-checker.py",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

这个 Hook 会在检测到 git commit 命令时自动运行三项检查,任一未通过则弹出确认提示,由用户决定是否继续。


安全警告与最佳实践

Hooks 能执行任意 Shell 命令,配置不当可能造成文件删除、信息泄露等后果。务必遵守以下原则:

安全清单

风险 防护措施
恶意脚本 只运行你自己编写或审查过的脚本,不要从不明来源复制配置
权限过大 脚本不使用 sudo,只请求必要权限
敏感信息 不在脚本中硬编码密码 / Token,使用环境变量
无限循环 设置合理的 timeout 值,避免脚本卡死
团队透明度 .claude/settings.json.claude/hooks/ 纳入代码审查

错误处理模板

每个 Hook 脚本都应包含以下防御逻辑:

#!/usr/bin/env python3
import sys
import json

try:
    input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
    # JSON 解析失败时静默退出,不影响 Claude 正常工作
    sys.exit(0)

# 业务逻辑...

# 无论执行结果如何,确保正常退出
sys.exit(0)

关键点:Hook 脚本出错不会阻止 Claude Code 运行,但该 Hook 功能会失效。始终用 try/except 包裹 JSON 解析,并在末尾显式 sys.exit(0)


FAQ

Hook 脚本支持哪些语言?

任何可以从命令行执行的程序都可以作为 Hook 脚本:Python(推荐,跨平台)、Bash(macOS/Linux)、Batch(Windows)、Node.js、Go 或 Rust 编译后的二进制文件。只要 command 字段指定的命令能在终端运行即可。

Hook 失败会影响 Claude 运行吗?

不会。Hook 脚本报错或超时只会导致该 Hook 本身功能失效,Claude Code 会继续正常工作。但建议为每个脚本添加完善的错误处理,避免静默失败导致你误以为规则在生效。

怎么调试 Hook?

三个方法:

  1. 手动模拟输入:用 echo 管道将模拟 JSON 传入脚本,观察 stdout 和 stderr 输出。
  2. 添加 stderr 日志:在脚本关键位置 print("DEBUG: ...", file=sys.stderr) 输出调试信息,会显示在 Claude Code 界面。
  3. 写入日志文件:将调试信息写入 ~/.claude/hooks-debug.log,方便事后分析。
echo '{"tool_name":"Write","tool_input":{"file_path":"test.js"}}' | python .claude/hooks/post-auto-format.py
# 观察 stderr 输出的调试信息

Hook 和 Commands(自定义斜杠命令)有什么区别?

Commands 是用户主动调用的快捷指令(如 /review),需要手动触发;Hooks 是事件驱动的自动化脚本,在满足条件时自动执行。Commands 适合"按需操作",Hooks 适合"强制规则"。两者可以配合使用:比如用 Command 触发代码审查,用 Hook 自动格式化审查后的修改。

Hooks 可以链式调用吗?

可以。在同一个 matcher 下配置多个脚本,它们会按顺序依次执行:

{
  "matcher": "Write",
  "hooks": [
    {"type": "command", "command": "python hook1.py", "timeout": 10},
    {"type": "command", "command": "python hook2.py", "timeout": 10}
  ]
}

但注意,对于 PreToolUse,如果前一个脚本返回 deny,后续脚本不会执行。建议将优先级最高的检查放在前面。


总结与下一步

本文覆盖了 Claude Code Hooks 系统的核心知识:

  1. Hooks = 确定性自动化:不依赖 AI 记忆,事件触发 100% 执行
  2. 6 种 Hook 类型:覆盖从用户输入到会话结束的完整生命周期
  3. 配置三要素:Hook 类型 + matcher 匹配器 + command 脚本
  4. 决策输出:PreToolUse 通过 allow / deny / ask / message 控制工具行为
  5. 三大实战场景:自动格式化、文件保护、Git 提交检查
  6. 安全第一:只用信任脚本,设置 timeout,完善错误处理

下一步阅读:

系列文章:本文是《Claude Code实战指南》系列第 6 篇,查看完整系列
💡 国内稳定访问 Claude: weelinking - 稳定、稳定、稳定

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐