Claude Code Hooks 完全拆解:用 Shell 命令接管 AI 的每个动作

Claude Code 的 Hooks 机制让你用 Shell 命令在 AI 生命周期的五个节点植入确定性控制——写文件后自动格式化、任务完成发通知、危险操作前强制拦截。本文完整拆解五种事件类型、配置结构、stdin 数据格式与 JSON 决策输出,附三个即开即用配置示例。

大多数人把 Claude Code 用成了一个更聪明的命令行聊天窗口——给任务,等回答,再给下一个任务。这没问题,但也意味着你在用 10% 的能力。
让 Claude Code 真正强大起来的,是它的 Hooks 机制:一套把 Shell 命令注入到 AI 生命周期特定节点的系统。写文件时自动格式化、任务完成时发 Telegram 通知、执行危险命令前强制拦截——这些都不依赖 Claude 自己决定要不要做,而是以确定性方式触发。

为什么 Hooks 比 CLAUDE.md 更可靠

CLAUDE.md 里的指令是给 Claude 看的——Claude 会尽力遵守,但它终究是语言模型的输出,不是代码的约束。你告诉它「每次写完文件都运行 lint」,它可能跑,也可能忘记。
Hooks 的逻辑不同。它是注册在 Claude Code 进程里的事件监听器,由 Claude Code 本身调度执行,不经过模型推理。退出码、JSON 输出、stdin 数据——这些是工程接口,不是 prompt。
官方文档对这个区别有明确表述:Hooks 的设计目标是「确定性地定制 Claude Code 行为,不依赖大模型自主选择执行」。1

五种生命周期事件

Claude Code 当前提供五个可挂载的生命周期节点:1
事件触发时机典型用途
PreToolUseClaude 生成工具参数后、工具执行前拦截风险命令、记录审计日志、强制格式检查
PostToolUse工具成功执行后写完文件自动 lint/format、提交后发通知
StopClaude 完成响应、会话结束时任务完成通知、收尾清理脚本
NotificationClaude 发送通知时将 AI 通知转发到外部渠道
UserPromptSubmit用户提交 prompt 前注入时间上下文、预处理输入
其中 Stop 是自动化场景里使用最频繁的。把耗时 20 分钟的 Claude 任务交给后台跑,完成后触发一条 Telegram 消息,比你盯着终端划算得多。2

配置结构与三个作用域

Hooks 写在 Claude Code 的 JSON 配置文件中,有三个作用域:
  • ~/.claude/settings.json:用户全局,对所有项目生效
  • .claude/settings.json:项目级,可提交 git 供团队共享
  • .claude/settings.local.json:本地个人配置,建议加入 .gitignore
配置的基础结构是「事件 → 匹配器 → 命令列表」:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "python -m black \"$file\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/send-telegram.sh 'Claude finished the task'"
          }
        ]
      }
    ]
  }
}
matcher 字段只对 PreToolUsePostToolUse 有效,支持精确字符串(Write)和正则(Edit|WriteNotebook.*);省略则匹配该事件下的所有工具调用。
StopNotification 没有 matcher 概念,直接列出 hooks 数组即可。

钩子如何控制 Claude 的决策

这是 Hooks 最被低估的一层:通过退出码或结构化 JSON,你的 Shell 脚本可以直接影响 Claude 是否继续执行。

用退出码传递状态

  • 退出码 0:成功,stdout 会在对话记录模式(Ctrl-R)里展示给用户
  • 退出码 2:阻塞错误,stderr 会回传给 Claude——对于 PreToolUse 这意味着工具调用被取消,Claude 会看到你的错误信息并重新决策;对于 Stop 则会阻止 Claude 停止并要求它继续处理
  • 其他退出码:非阻塞错误,只展示 stderr,不打断流程

用 JSON 输出精细控制

当你的脚本输出合法 JSON 时,Claude Code 会解析它并执行更精细的控制。PreToolUse 钩子可以输出:
{
  "decision": "block",
  "reason": "检测到生产环境配置文件写入,已拦截。请确认操作范围后重试。"
}
reason 会直接发送给 Claude,Claude 会基于这条信息重新规划。设置 "decision": "approve" 则会绕过 Claude Code 的原有权限系统直接放行。1
Stop 钩子支持 "decision": "block"——这可以用来实现「任务完成前强制验证」:Claude 认为自己做完了,但你的验证脚本发现测试没过,钩子返回 block + reason,Claude 收到信息后继续修复。

三个实用配置示例

1. Python 文件写入后自动 Black 格式化

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.py)",
        "hooks": [
          {
            "type": "command",
            "command": "python -m black \"$file\""
          }
        ]
      }
    ]
  }
}
每次 Claude 写完 .py 文件,Black 立即跑一遍,不需要 Claude 记住「要格式化」这件事。

2. 任务完成后发 macOS 系统语音通知

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "say 'Claude has finished the task'"
          }
        ]
      }
    ]
  }
}
macOS 自带的 say 命令,零依赖。适合后台跑长任务时切出去做别的事。Telegram/Slack 通知同理,把 say 替换成对应 API 调用脚本即可。

3. 拦截生产配置文件写入

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\npath = data.get('tool_input', {}).get('file_path', '')\nif 'production' in path or '.env' in path:\n    print(json.dumps({'decision': 'block', 'reason': f'拦截生产文件写入: {path}'}))\nelse:\n    print(json.dumps({'decision': 'approve'}))\n\""
          }
        ]
      }
    ]
  }
}
每次 Write 工具触发前,脚本从 stdin 读取 tool_input.file_path,检查路径是否包含 production.env,命中则 block 并把原因告诉 Claude。

Hooks 读取的 stdin 数据结构

脚本通过 stdin 接收 JSON 格式的上下文,不同事件携带不同字段:1
字段所有事件
session_id当前会话 ID
transcript_path完整对话记录文件路径
PreToolUsePostToolUse 额外携带:
  • tool_name:工具名称(WriteBashEdit 等)
  • tool_input:工具入参,结构随工具类型变化(比如 Write 包含 file_pathcontent
  • tool_response(仅 PostToolUse):工具执行结果
这意味着你的脚本可以拿到完整的工具调用上下文——你知道 Claude 想写什么文件、想运行什么命令,然后再决定放行还是拦截。

与 MCP 工具的搭配

如果你用了 MCP 服务器扩展 Claude Code 的工具集,MCP 工具的命名格式是 mcp__[服务器名]__[工具名]。matcher 支持正则,可以批量匹配一个服务器的所有写操作:
{
  "matcher": "mcp__.*__write.*",
  "hooks": [{ "type": "command", "command": "/scripts/validate-mcp-write.py" }]
}
这样无论是哪个 MCP 服务器触发的写操作,都会先经过你的验证脚本。1

快速上手:5 分钟最小配置

如果你从没用过 Hooks,可以从这一个文件开始:
// .claude/settings.json
{
  "permissions": {
    "allow": ["Bash(git:*)", "Bash(npm:*)"]
  },
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "say 'Claude has finished'"
          }
        ]
      }
    ]
  }
}
/hooks 命令里也可以交互式配置,不需要手写 JSON。添加 Stop 通知,然后把 Claude Code 分配给一个耗时任务,自己去做别的事——这比「什么是 PreToolUse 的 JSON schema」更快让你感受到 Hooks 的价值。

边界与注意事项

Hooks 在个人项目和中小团队场景已经足够稳定。有几个限制值得提前知道:
  • --dangerously-skip-permissions 慎用:在自动化脚本里跑 Claude Code 时经常会用到这个标志来绕过所有权限确认,风险较高,务必配合 permissions.allow 白名单使用,而不是图省事什么都放行。2
  • Stop 钩子的 stop_hook_active 字段:如果你在 Stop 钩子里触发了新的 Claude 操作,可能导致无限循环。官方文档里会在 Stop 事件的 stdin 携带 stop_hook_active 标记,用来检测当前是否已经在一次钩子触发的流程里。1
  • 成本:Hooks 本身不消耗 token,但 decision: block 让 Claude 继续处理、或 Stop 钩子里触发了新任务,都会产生额外的 token 消耗。
Hooks 的完整文档在 docs.anthropic.com/en/docs/claude-code/hooks,生命周期事件的 stdin 数据结构和 JSON 输出 schema 都在里面,写复杂拦截脚本前值得读一遍。
正在加载链接预览…

围绕这条内容继续补充观点或上下文。

  • 登录后可发表评论。