Hooks 怎么用?5 个能直接抄的自动化配置

Hooks 怎么用?5 个能直接抄的自动化配置

技术系列第 2 篇。上一篇《CLAUDE.md 怎么写》说过:CLAUDE.md 是说明,权限和 Hook 才是门锁。这篇就把"锁"装上------5 个拿来即用的 hook 配置。

一、为什么需要 Hooks

假设你正用 Claude Code 维护一个项目。它干活利落,但有些事你每次都要叮嘱:改完记得格式化、别碰 .env、删库命令悠着点、写完做下检查。可它是 AI 不是员工,叮嘱不进考核:漏一次,格式乱了;漏两次,.env 里的密钥可能已经被读走。

这类"每次都必须发生"的事,靠反复口头强调不靠谱,写进 CLAUDE.md 也只是"说明",模型可能遵循也可能忘。稳的办法是把它变成流程:让 Claude Code 在特定动作前后自动执行你指定的命令。这就是 Hooks------你在关键动作上挂一段命令,到点自动执行,执行结果还能反过来影响流程。从此规矩不靠自觉,靠机制兜底。

二、配置写在哪、长什么样

Hooks 不写在 CLAUDE.md 里,而是写在 settings.json 中,共三个位置:

  • 项目级 .claude/settings.json:随项目提交进 git,全组共享
  • 个人级 ~/.claude/settings.json:对你的所有项目生效
  • 本地级 .claude/settings.local.json:不提交,适合先拿来做试验

配置结构是固定的三层嵌套:

json 复制代码
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{ "type": "command",
        "command": "python .claude/hooks/fmt.py" }]
    }]
  }
}

对应关系记三点:

  1. 事件名决定"什么时候触发"。常用的有:PreToolUse(工具执行前,可拦截)、PostToolUse(工具执行后)、UserPromptSubmit(你提交提示词时)、Stop(一轮回答结束时)、SessionStart(会话启动)、Notification(需要你注意时)。事件不止这些,以官方文档为准。
  2. matcher 决定"对哪些工具生效",写正则,如 Edit|Write、Bash。只有 PreToolUse 和 PostToolUse 需要 matcher;Stop 这类事件与具体工具无关,不用写。
  3. command 是真正执行的 shell 命令。建议 JSON 里只留一行 python 某脚本.py,逻辑全放脚本文件里------转义省心,审查也方便。

三、工作原理:事件、匹配、执行、退出码

一次工具调用会走完这样一条时间线,hook 在其中两个节点出手:

hook 命令启动时,Claude Code 会把一段 JSON 从标准输入喂给它:Bash 的命令在 tool_input.command,Edit/Write 的目标路径在 tool_input.file_path。比如你想拦一条危险命令,脚本实际收到的大致长这样(有删减):

json 复制代码
{ "tool_name": "Bash",
  "tool_input": { "command": "git push --force origin main" } }

脚本解析输入、做出判断,再用退出码表态------0 放行,2 阻断(stderr 的内容会反馈给 Claude),其他非零是非阻断错误,流程照常继续。更复杂的 JSON 输出控制方式本文不展开,以官方文档为准。记住退出码这个协议,下面的配置就都能看懂了。

四、5 个能直接抄的配置

先看全景,每一行都对应下文一节:

约定:脚本统一放在项目的 .claude/hooks/ 目录下。Windows 用户注意,路径含空格时 command 里的路径要加引号。安装任何一条都是同样五步:建目录、存脚本、把配置粘进 settings.json、会话里输入 /hooks 确认加载、随手找个该触发的地方试一把。

① 改完代码自动格式化

json 复制代码
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{ "type": "command",
        "command": "python .claude/hooks/format_changed.py" }]
    }]
  }
}
lua 复制代码
import json, os, shutil, subprocess, sys

PRETTY = ["npx", "prettier", "--write"]
FORMATTERS = {".py": ["python", "-m", "black", "--quiet"], ".ts": PRETTY, ".js": PRETTY,
              ".jsx": PRETTY, ".tsx": PRETTY, ".css": PRETTY, ".html": PRETTY}


def main() -> int:
    data = json.load(sys.stdin)
    path = data.get("tool_input", {}).get("file_path", "")
    cmd = FORMATTERS.get(os.path.splitext(path)[1].lower())
    if not cmd or not os.path.isfile(path):
        return 0  # 非代码文件或路径不存在:安全跳过
    if shutil.which(cmd[0]) is None:
        return 0  # 未安装格式化工具:跳过,不报错
    subprocess.run(cmd + [path])
    return 0


sys.exit(main())

从标准输入取 file_path,按扩展名挑格式化命令。适用:任何多人协作或在意风格的项目。注意两点:没装的格式化工具会被安全跳过,所以先把 black 或 prettier 装好;格式化失败不返回 2,避免格式问题拦死流程。

② 拦截危险命令

json 复制代码
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command",
        "command": "python .claude/hooks/block_danger.py" }]
    }]
  }
}
python 复制代码
import json, re, sys

RULES = [
    (r"\brm\s+(-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r)", "递归强制删除(rm -rf)"),
    (r"\bgit\s+push\s+.*(--force|-f\b|--force-with-lease)", "强制推送远程分支"),
    (r"\bdrop\s+table\b|\btruncate\s+table\b", "清空数据库表"),
    (r"\bmkfs\b|\bdd\s+if=", "格式化/覆写磁盘"),
]

def main() -> int:
    sys.stderr.reconfigure(encoding="utf-8")  # 防止中文 Windows 下乱码
    raw = json.load(sys.stdin).get("tool_input", {}).get("command", "")
    cmd = re.sub(r"\s+", " ", raw.lower())  # 大小写、多余空格先归一化
    for pat, why in RULES:
        if re.search(pat, cmd):
            print(f"已拦截:{why}", file=sys.stderr)
            return 2  # 退出码 2 = 阻断,stderr 会反馈给 Claude
    return 0

sys.exit(main())

命中黑名单就 exit 2,Claude 当场收到拒绝原因,会换个安全做法。适用:怕误删数据、怕强推覆盖同事提交的日常开发。注意:这是关键词匹配不是完整解析,绕过写法防不住,名单请按项目自己增删,它挡的是"手滑"不是"故意"。

③ 保护敏感文件

json 复制代码
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Edit|Write|Read",
      "hooks": [{ "type": "command",
        "command": "python .claude/hooks/guard_secret.py" }]
    }]
  }
}
lua 复制代码
import json, os, sys

BAD_ENDS = (".pem", ".key", ".p12", ".pfx")
BAD_NAMES = ("id_rsa", "id_ed25519", "id_ecdsa")

def is_sensitive(path: str) -> bool:
    name = os.path.basename(path).lower()
    if name == ".env" or name.startswith(".env."):
        return True  # .env、.env.local 等变体都算
    return name in BAD_NAMES or name.startswith("id_rsa.") or name.endswith(BAD_ENDS)

def main() -> int:
    sys.stderr.reconfigure(encoding="utf-8")  # 防止中文 Windows 下乱码
    p = json.load(sys.stdin).get("tool_input", {}).get("file_path", "")
    if is_sensitive(p):
        print(f"已拦截:{p} 疑似密钥或环境变量文件", file=sys.stderr)
        return 2
    return 0

sys.exit(main())

判断不盯全路径盯文件名,所以 .env.local、config/.env.production 这类变体也逃不掉。适用:仓库里有真实密钥、或想让 AI 连读都不读密钥的场景。注意:env.example 这种示例文件不会被误伤;名单是保守起点,按需增删。

④ 任务完成提醒

css 复制代码
{
  "hooks": {
    "Stop": [{
      "hooks": [{ "type": "command",
        "command": "powershell -c "[console]::beep(880,200)"" }]
    }]
  }
}

这条不需要脚本,一行命令即可。Stop 没有 matcher,Claude 每答完一轮就响一声。其他系统换个命令:macOS 用 osascript -e 'display notification "任务完成" with title "Claude Code"',Linux 用 notify-send "Claude Code" "任务完成"------我只在 Windows 实测过响铃,其余以你的环境为准。适用:挂着长任务切去做别的事的人。注意:别用弹窗类命令,太吵的提醒活不过三天。

⑤ 改完自动跑检查

json 复制代码
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{ "type": "command",
        "command": "python .claude/hooks/check_changed.py" }]
    }]
  }
}
sql 复制代码
import json, os, subprocess, sys

CHECK = ["python", "-m", "py_compile"]  # 换成你项目的快速检查,如 npx tsc --noEmit

def main() -> int:
    sys.stderr.reconfigure(encoding="utf-8")  # 防止中文 Windows 下乱码
    path = json.load(sys.stdin).get("tool_input", {}).get("file_path", "").replace("\", "/")
    if "/src/" not in f"/{path}" or not os.path.isfile(path):
        return 0  # 只看 src 下的改动,其余跳过
    r = subprocess.run(CHECK + [path], capture_output=True, text=True, encoding="utf-8", errors="replace")
    if r.returncode:
        print(r.stderr[-1200:], file=sys.stderr)
        return 2  # 检查不过:让 Claude 直接看到报错
    return 0

sys.exit(main())

检查失败就 exit 2,报错文本直接进 Claude 的眼睛,它下一轮就自己修,不用你当传话筒。适用:TypeScript、Python 这类有秒级检查手段的项目。注意:必须快------PostToolUse 是同步的,hook 慢一秒,每次修改都慢一秒;所以只跑单文件检查、且只对 src 目录生效,整包测试留给 CI。

五、调试方法与常见的坑

配了没反应,按顺序查:

  1. 配置改完不是即插即用,会话里输入 /hooks 确认已加载、挂对了位置
  2. 用 python -m json.tool settings.json 验证 JSON 语法,少个逗号就全盘失效
  3. 手动喂 JSON 测脚本:echo '{"tool_input":{"command":"rm -rf /"}}' | python block_danger.py,看退出码和 stderr 是否符合预期
  4. Windows 的双重转义最容易翻车:JSON 里的引号要写成 ",命令一复杂就老老实实写成脚本文件
  5. 路径含空格必须加引号;hook 变慢就检查是不是把重型命令挂在了 PostToolUse

还有两个容易忽略的细节。一是触发频率 :Stop、Notification 这类事件每一轮对话都会触发,UserPromptSubmit 每次提交都触发,挂上命令前先想想"它一天会被调用几百次吗",提醒类尤其要轻。二是怎么停用 :不想要了就把对应配置从 settings.json 里删掉,或者先挪到 settings.local.json 里观察几天;改完同样用 /hooks 复核,确认旧配置已经不在列表里。

六、安全提醒:这是把真钥匙

Hooks 以你的用户权限执行任意 shell 命令,等于交出账号级别的权限,所以:

  • 抄配置前逐字读 command。来路不明、看不懂、带下载执行的,一律不抄
  • 拦截类 hook 是兜底不是唯一防线,和权限规则配合用,别因为挂了 hook 就全程免确认
  • 改完配置用 /hooks 复核;脚本里不要写死任何密钥
  • 从标准输入读到的 JSON 是输入不是信任,脚本内先校验再使用

结语

Hooks 的价值在于把"每次都要叮嘱"变成"每次自动发生":格式化交给 ①,底线交给 ②③,体验交给 ④,质量交给 ⑤。建议从 ④ 开始试水,确认机制跑通,再上会拦人的 ②③。

相关推荐
卷无止境1 小时前
Python 3.14 自由线程:拆掉 GIL 之后,我们到底得到了什么
后端·python
WL学习笔记2 小时前
模板初阶(C++)
开发语言·c++·后端·模板初阶
九零HTTP2 小时前
aiohttp 连接池:那个 limit 参数到底在限什么
后端
花间相见2 小时前
【计算基础|MySQL06】MySQL 架构与一条 SQL 的旅程
数据库·后端
Thneonl2 小时前
Spark 比 MR 快在哪?管住最贵的 shuffle
后端·架构
申君健24864182772022 小时前
AI SDK 概念全景总结(v7) 二
后端
申君健24864182772023 小时前
AI SDK 概念全景总结(v7)
后端
imDwAaY3 小时前
SQL 查询突然变慢了?一篇讲清慢 SQL 排查思路
后端·mysql