<!-- title: Hooks拦截工具调用：造自动化安全护栏 -->

## 概述

本教程带你掌握 Claude Code 的 **Hooks（生命周期钩子）** 系统——一种在工具执行、对话轮次、会话启停等关键节点自动触发用户脚本的能力。学完后你将能够：用 `PreToolUse` 在危险命令执行前拦下它、用 `PostToolUse` 在文件写入后自动格式化、用 `UserPromptSubmit` 给每条提问注入项目上下文、用 `Notification` 在 Claude 等待确认时桌面提醒。Hooks 是把 Claude Code 从"陪你聊天的助手"变成"受控的自动化执行体"的核心机制，适合已有基础配置经验、想让 Agent 真正无人值守且安全可控的开发者。

## 前置条件

- 已安装 Claude Code CLI（v2.x 及以上，Hooks 的决策控制、异步事件、MCP 工具匹配等能力均较新）
- 熟悉 JSON 配置文件与 shell 脚本（bash/zsh）
- 理解 Claude Code 的工具调用模型（Bash、Edit、Write 等）
- 一台可访问终端的 macOS/Linux/WSL2 机器
- 建议先读过 `CLAUDE.md` 与 settings.json 的基本配置，了解权限（permissions）概念

---

## 正文

### 第一步：理解 Hooks 的三层运行节奏

Hooks 不是单一触发点，而是分布在 Claude Code 会话全生命周期的一组事件。按触发频率，它们归为三种节奏（cadence），记住这个分类，是设计护栏的前提：

```mermaid
flowchart TD
    S([会话开始]) --> SS[SessionStart<br/>每会话一次]
    SS --> Loop[进入对话轮次循环]
    Loop --> UPS[UserPromptSubmit<br/>每轮提问一次]
    UPS --> Agent[Agent 工具调用循环]
    Agent --> Pre{PreToolUse<br/>每次工具调用前}
    Pre -->|allow/放行| Exec[工具执行]
    Pre -->|deny/拦截| Block[阻断并反馈]
    Exec --> Post{PostToolUse<br/>每次工具调用后}
    Post --> Side[副作用:格式化/日志/校验]
    Side --> More{还有工具调用?}
    More -->|是| Pre
    More -->|否| Stop[Stop<br/>本轮回答结束]
    Block --> Stop
    Stop --> Loop
    Loop --> End([会话结束])
    End --> SE[SessionEnd<br/>每会话一次]
```

- **每会话一次（once per session）**：`SessionStart`、`SessionEnd`。适合初始化环境、加载技能、收尾清理。
- **每轮一次（once per turn）**：`UserPromptSubmit`（用户提交提问时）、`Stop`（Claude 结束本轮回答时）、`StopFailure`。
- **每次工具调用（on every tool call）**：`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`PostToolBatch`。这是护栏的核心战场。

此外还有一批**异步事件**（async events），如 `Notification`、`PreCompact`、`PostCompact`、`SubagentStart`、`SubagentStop`、`TaskCreated`、`TaskCompleted`、`ConfigChange`、`InstructionsLoaded`、`CwdChanged`、`FileChanged`。它们不阻塞主流程，仅产生副作用（发通知、写日志）。

**预期结果**：在脑海里建立"会话→轮次→工具调用"的三层嵌套视图，知道每类钩子挂在哪一层。

### 第二步：掌握钩子的四种通信通道

Claude Code 与你的钩子脚本之间有四条通道，理解它们的协议是写出可靠钩子的关键：

| 通道 | 方向 | 用途 |
|------|------|------|
| **stdin** | Claude Code → 脚本 | 传入事件上下文（JSON），含工具名、参数、会话 ID、cwd 等 |
| **stdout** | 脚本 → Claude Code | 传回 JSON 结构化决策（仅在 exit 0 时被解析） |
| **stderr** | 脚本 → 用户 | 反馈信息，exit 2 时会展示给用户 |
| **exit code** | 脚本 → Claude Code | 0 放行、2 阻断、1 非阻断错误 |

**关键陷阱**：Claude Code 仅在退出码为 0 时解析 stdout 中的 JSON；若 exit 2，任何 JSON 都被忽略。这与传统 Unix 习惯相反——exit 1 被当作"非阻断错误"，Claude Code 照常继续执行动作。所以"想阻止一个动作"，你必须用 **exit 2** 或 exit 0 + JSON `permissionDecision: "deny"`，绝不能用 exit 1。

一个最小可运行的钩子脚本（读取 stdin、打印决策）长这样：

```bash
#!/usr/bin/env bash
# 读取 Claude Code 经 stdin 传入的事件 JSON
input=$(cat)

# 提取工具名（用 jq 解析）
tool_name=$(echo "$input" | jq -r '.tool_name // empty')

if [ "$tool_name" = "Bash" ]; then
  cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
  # 检测危险命令
  if echo "$cmd" | grep -qE 'rm -rf /|sudo '; then
    echo "检测到危险命令: $cmd" >&2
    exit 2   # exit 2 = 阻断
  fi
fi

exit 0       # exit 0 = 放行
```

**常见问题**：脚本没有 `#!/usr/bin/env bash` 头、没有执行权限（`chmod +x`）、依赖 `jq` 但环境未安装——这三点占了新手 90% 的失败原因。可先用 `claude --debug` 查看钩子的实际调用与报错。

### 第三步：在 settings.json 中注册钩子

钩子配置写在 settings.json 的 `hooks` 字段下。settings.json 存在三个层级，按优先级从高到低为：企业策略 > 用户级（`~/.claude/settings.json`）> 项目级（`.claude/settings.json`）。一个事件下可挂多条钩子，按顺序执行：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-secrets.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/autoformat.sh" }
        ]
      }
    ]
  }
}
```

注意新版结构是 `事件名 → [ {matcher, hooks:[{type, command}]} ]`。`type` 目前支持 `command`（shell 脚本）、`http`（HTTP 端点）、`prompt`（LLM 提示）。`$CLAUDE_PROJECT_DIR` 是 Claude Code 注入的项目根环境变量，钩子里随时可用。

**Matcher（匹配器）** 决定钩子是否触发，规则因包含字符而异：纯工具名（如 `Bash`）精确匹配；含 `|` 表示多工具（`Edit|Write`）；含正则元字符则按正则解析，例如 `mcp__.*__write.*` 能匹配所有 MCP 服务器下名字含 write 的工具，`mcp__memory__.*` 匹配 memory 服务器的全部工具。留空 matcher 的钩子对该事件**每次都触发**。

**预期结果**：配置写入后重启会话，对一个 Write 操作应能看到对应 PostToolUse 钩子被调用（在 `claude --debug` 日志里）。

### 第四步：用 PreToolUse 决策控制精准拦截

`PreToolUse` 在工具执行**之前**触发，且无论当前权限状态如何都会运行——这意味着它是你最后一道确定性防线。用退出码只能"阻断或沉默"，而用 JSON 决策可以实现更精细的控制。在 exit 0 时向 stdout 打印结构化 JSON：

```bash
#!/usr/bin/env bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')

# 规则:禁止任何 force push 与 main 分支操作
if echo "$cmd" | grep -qE 'git push .* --force|git push -f|git checkout main|git reset --hard'; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "禁止强制推送与对 main 的破坏性操作（由护栏钩子拦截）"
  }
}
EOF
  exit 0
fi

exit 0
```

`permissionDecision` 三种取值：

- **`"deny"`**：阻断该工具调用，把 reason 反馈给 Claude，它会调整策略重试。
- **`"allow"`**：显式放行，并可作为"自动批准但有安全网"模式——配合较宽松的权限策略，让钩子做守门员。
- **`"defer"`**：交还给正常权限流程判断（不覆盖默认行为）。

当同一事件挂了多个 PreToolUse 钩子且返回不同决策时，有明确的优先级规则（deny 通常优先于 allow），官方文档对此有完整定义，复杂场景务必查阅"PreToolUse decision control"小节确认。

**常见问题**：返回了 `permissionDecision: "deny"` 但 Edit 工具仍被执行——这通常是 JSON 格式错误（多余逗号、未转义引号）导致解析失败而回退为放行。务必用 `jq -n` 构造 JSON 而非手拼字符串，避免命令中的特殊字符破坏结构。

### 第五步：用 PostToolUse 做自动格式化与校验

`PostToolUse` 在工具执行**之后**触发，最适合副作用类工作：自动 lint、格式化、运行类型检查、写审计日志。它不能撤销已发生的操作，但可以在文件落盘后立即修正。

一个典型的"写完即格式化"钩子：

```bash
#!/usr/bin/env bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# 仅处理项目内的源码文件
case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    # 自动用项目配置格式化
    npx prettier --write "$file_path" >/dev/null 2>&1
    # 运行类型检查,失败则反馈给 Claude
    if ! npx tsc --noEmit "$file_path" >/dev/null 2>&1; then
      echo "类型检查未通过,请修复 $file_path" >&2
    fi
    ;;
  *.py)
    ruff format "$file_path" >/dev/null 2>&1
    ruff check --fix "$file_path" >/dev/null 2>&1
    ;;
esac
exit 0
```

`PostToolUse` 同样支持 JSON 决策控制（`PostToolUseFailure` 处理工具调用失败的情况），并能在 `hookSpecificOutput` 里返回额外上下文。由于它在每次工具调用后都跑，**务必让脚本快速返回**（毫秒级），否则会拖慢整个 Agent 循环。重活（全量测试套件）留给 `Stop` 钩子。

### 第六步：用 UserPromptSubmit 注入上下文

`UserPromptSubmit` 在用户按下回车、Claude 看到提问之前触发。它最强大的用法是**为每条提问附加额外上下文**，无需用户手动维护。这比把所有背景塞进 `CLAUDE.md` 更动态——可以根据当前 git 分支、时间、文件状态实时生成。

```bash
#!/usr/bin/env bash
branch=$(git branch --show-current 2>/dev/null)
uncommitted=$(git status --porcelain 2>/dev/null | wc -l)
today=$(date +%Y-%m-%d)

# exit 0 + additionalContext 注入到提问上下文
cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "[自动注入] 当前分支: ${branch} | 未提交变更: ${uncommitted} 个文件 | 日期: ${today}。回答请考虑当前分支上下文。"
  }
}
EOF
exit 0
```

注意：`UserPromptSubmit` 用 exit 0 + JSON 的 `additionalContext` 来补充上下文；若想直接**拦截/拒绝**某条提问，则用 exit 2（stderr 反馈给用户，提问不会送达 Claude）。这能实现"工作时间外禁止提问"或"提问必须包含任务编号"等软约束。

### 第七步：用 Notification 做桌面提醒

当 Claude 需要你确认权限、或长时间空闲时，它会触发 `Notification` 事件。配置一个钩子把通知转发到桌面/手机，你就无需一直盯着终端。macOS 示例：

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          { "type": "command", "command": "osascript -e 'display notification \"Claude 需要确认权限\" with title \"Claude Code\" sound name \"Glass\"'" }
        ]
      }
    ]
  }
}
```

`matcher` 在 Notification 事件上按通知类型过滤（如 `permission_prompt`、`idle_prompt`）。Notification 钩子是**纯副作用**，不能阻断或修改通知本身。在 Linux 上可换成 `notify-send`，或转发到 Slack/钉钉 webhook 实现移动办公。

---

## 实战案例：构建一个完整的安全护栏系统

让我们把上述能力组装成一个真实可用的护栏体系。目标项目是一个 Web 服务，Claude Code 负责日常开发，但必须满足：

1. **绝不执行** `rm -rf` 根目录、`git push --force` 等破坏性命令
2. **绝不写入** 含密钥/令牌的文件（防泄密提交）
3. 每次写文件**自动格式化**
4. 每轮提问**自动注入**当前分支与变更数
5. Claude 等待确认时**桌面通知**

项目目录结构：

```
my-service/
├── .claude/
│   ├── settings.json
│   └── hooks/
│       ├── guard-bash.sh
│       ├── no-secrets.sh
│       ├── autoformat.sh
│       ├── inject-context.sh
│       └── notify.sh
├── CLAUDE.md
└── src/
```

**`.claude/settings.json`**（项目级配置，随仓库分发，团队共享护栏）：

```json
{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh" }] },
      { "matcher": "Write|Edit|MultiEdit", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-secrets.sh" }] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/autoformat.sh" }] }
    ],
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/inject-context.sh" }] }
    ],
    "Notification": [
      { "matcher": "permission_prompt", "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh" }] }
    ]
  }
}
```

**`no-secrets.sh`**（防止密钥落盘——PreToolUse 在写入前拦截）：

```bash
#!/usr/bin/env bash
set -euo pipefail
input=$(cat)
content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string // ""')

# 常见密钥模式:AWS、OpenAI、私钥头
if echo "$content" | grep -qiE 'AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{20,}|-----BEGIN (RSA |EC )?PRIVATE KEY-----'; then
  cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "检测到疑似密钥/私钥，已拦截写入。请改用环境变量或密钥管理服务。"
  }
}
EOF
  exit 0
fi
exit 0
```

测试流程：在一个交互会话里，让 Claude "把 AWS 密钥 AKIA... 写进 config.py"——你会看到 deny 反馈，文件不会被创建。再让它 "执行 `rm -rf node_modules /`"——guard-bash 钩子拦截。而正常写代码会被 PostToolUse 自动 prettier 格式化。每条提问顶部的注入上下文让你随时知道当前分支状态。需要确认时桌面弹窗响起。

整个护栏系统的请求-拦截-反馈闭环如下：

```mermaid
sequenceDiagram
    participant U as 用户
    participant CC as Claude Code
    participant H as 钩子脚本
    participant T as 工具(Bash/Edit)

    U->>CC: "把密钥写进 config.py"
    CC->>H: PreToolUse(Edit) JSON via stdin
    H->>H: 正则匹配到 AKIA 密钥
    H-->>CC: exit0 + permissionDecision: deny
    CC-->>CC: 拦截,将 reason 反馈给模型
    CC-->>U: "已拦截:检测到密钥,建议用环境变量"
    Note over U,T: 危险写入从未发生

    U->>CC: "给 utils.ts 加个工具函数"
    CC->>H: PreToolUse(Edit) 检查通过
    H-->>CC: exit0 放行
    CC->>T: 执行 Edit 写入
    T-->>CC: 完成
    CC->>H: PostToolUse(Write)
    H->>H: prettier --write 格式化
    H-->>CC: exit0
    CC-->>U: 已完成并自动格式化
```

这就是一个"自动批准但有安全网"的工作流：权限策略可以适度放宽，因为钩子守门员会兜住真正的红线，既保持流畅又守住底线。

---

## 进阶：异步钩子、HTTP 钩子与超时控制

随着规模扩大，几个进阶点值得关注：

- **HTTP 钩子（type: http）**：把决策逻辑部署成远端服务，Claude Code 通过 POST 请求体传入同样的 JSON，通过响应体返回决策。适合需要共享状态、跨项目复用、或调用公司内部风控 API 的场景。
- **异步事件**：`FileChanged`、`CwdChanged`、`InstructionsLoaded`、`TaskCreated`、`TaskCompleted` 等不阻塞主循环，适合做可观测性埋点（把每次工具调用与子任务完成记入 JSONL 日志，用于事后审计与性能分析）。
- **超时与失败**：每条钩子可配 `timeout`（默认 60 秒）。超时或崩溃的钩子会被记为失败，但默认不会中断会话；对必须成功的护栏，应在脚本内做好防御性编程与超时控制。
- **子代理（Subagent）隔离**：`SubagentStart`/`SubagentStop` 让你能追踪并行编排的子任务生命周期，结合日志可还原复杂的 fan-out 执行图。

一个值得养成的工程习惯：用 `claude --debug` 启动会话调试钩子，它会把每个钩子的 stdin/stdout/exit code 完整打印出来，是排查"为什么我的钩子没生效/被忽略"的利器。

---

## 延伸阅读

- **Automate actions with hooks（官方快速上手）**：`https://code.claude.com/docs/en/hooks-guide` —— 从零配置到第一个可用钩子的官方教学
- **Hooks reference（官方完整参考）**：`https://code.claude.com/docs/en/hooks` —— 事件清单、JSON schema、退出码语义、决策控制的权威定义
- **Claude Code Hooks: Complete Guide with 20+ Examples**：`https://aiorg.dev/blog/claude-code-hooks` —— 20+ 可复制粘贴的实战配置（格式化、审计、安全护栏）
- **Building Guardrails with PreToolUse**：`https://dev.to/mikelane/building-guardrails-for-ai-coding-assistants-a-pretooluse-hook-system-for-claude-code-ilj` —— 深入 PreToolUse 拦截系统的设计思路
- **Claude Code Hooks schemas (Gist)**：`https://gist.github.com/FrancisBourre/50dca37124ecc43eaf08328cdcccdb34` —— 输入输出 JSON 结构的实现级速查表

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| **适合的训练营类型** | 企业级 AI 编码训练营、DevOps 自动化进阶营、安全合规导向的 AI 工程营。本主题强调"可控、可审计、防事故"，契合企业落地诉求 |
| **学员基础要求** | 中级：需懂 shell 脚本、JSON、git，最好已完成 Claude Code 入门与权限配置章节 |
| **预计教学时长** | 3–4 小时（含动手实验）。建议拆为：理论 45 分钟、五个钩子逐个实现 90 分钟、完整护栏项目实战 60 分钟、调试与排错 30 分钟 |
| **实验产出** | 一套随仓库分发的 `.claude/settings.json` + 钩子脚本，可作为学员回到真实项目后直接复用的资产 |
| **风险提示** | 钩子是确定性拦截，但规则靠正则，存在误报/漏报。教学时应强调：护栏是"深度防御"的一层，不替代 code review 与最小权限原则；禁止依赖单一钩子保障生产安全 |

## 参考来源

1. [Hooks reference - Claude Code Docs](https://code.claude.com/docs/en/hooks) — 官方权威参考：事件清单、JSON schema、退出码与决策控制语义
2. [Automate actions with hooks - Claude Code Docs](https://code.claude.com/docs/en/hooks-guide) — 官方快速上手指南与示例
3. [Claude Code Hooks: Complete Guide with 20+ Ready-to-Use Examples (2026)](https://aiorg.dev/blog/claude-code-hooks) — 20+ 可复用的格式化、审计、护栏配置
4. [Building Guardrails for AI Coding Assistants: A PreToolUse Hook System](https://dev.to/mikelane/building-guardrails-for-ai-coding-assistants-a-pretooluse-hook-system-for-claude-code-ilj) — PreToolUse 拦截系统的深度设计实践
5. [Claude Code: Auto-Approve Tools While Keeping a Safety Net with Hooks](https://dev.to/abhaygawade/claude-code-auto-approve-tools-while-keeping-a-safety-net-with-hooks-4839) — "自动批准+安全网"模式的工程化方案

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
