---
title: "10 · 怎么让操作 100% 执行"
description: "Hooks 不是提醒 Claude Code 记得做事，而是在工具调用、提示提交、停止前后设置确定性检查点。能容忍遗漏写 CLAUDE.md，不能容忍就用 Hook。"
canonical: "https://xiangyugongzuoliu.com/tag/claude-code"
verifiedAt: "2026-05-08"
---

Aaron有一条工程习惯：凡是“忘一次就会出事”的规则，不要只写进提示词里。提示词让 AI 尽量记住，Hooks（钩子）让系统在关键节点强制检查。前者是提醒，后者是门禁。——Aaron

> [!NOTE]
> **这一篇用 14 分钟换什么**：第 9 篇讲 MCP 让 Claude Code 够到外部系统。够得到以后，风险也变大了。这一篇讲 Hooks：怎么在 Claude Code 准备调用工具、调用之后、提交提示、准备停止等时间点插入自动检查，让“必须发生的事”不再只靠 Claude 记得。

## 1. 先从一个危险的小动作开始

你在 `CLAUDE.md` 里写了一条规则：

```md
永远不要修改 `.env` 文件。
```

这条规则有用吗？有用。Claude Code 会读到，也大概率遵守。

但“大概率”不是“保证”。

想象某一次任务里，上下文很长、文件很多、你又让它“直接修好本地配置”。Claude 准备写 `.env`。如果只靠 `CLAUDE.md`，它可能会想起规则，也可能在复杂上下文里漏掉。

而 `.env` 不是普通文件。改坏一次，可能就是数据库连接、生产 token、第三方密钥出问题。

这时你不应该再问“怎么让提示词写得更严厉”。你应该换一个机制。

> [!IMPORTANT]
> **第一性原理**：Hooks 解决的不是“Claude 不理解规则”，而是“某些规则不能靠模型记忆来执行”。

Claude Code 官方 [Hooks guide](https://code.claude.com/docs/en/hooks-guide) 对 Hooks 的定位很清楚：Hooks 会在 Claude Code 生命周期的特定点自动运行，用来 enforce project rules、automate repetitive tasks、integrate with existing tools。也就是说，它是确定性自动化，不是又一段劝 Claude 听话的提示词。

## 2. CLAUDE.md、Permissions、Hooks 各管什么

先把三个概念摆清楚。

| 机制 | 中文理解 | 强度 | 适合什么 |
|------|------|------|------|
| `CLAUDE.md` | 工作说明书 | 建议 / 习惯 | 风格、流程、偏好、项目背景 |
| Permissions | 权限闸门 | 允许 / 询问 / 拒绝 | 工具级访问控制 |
| Hooks | 自动检查点 | 到点必触发 | 格式化、审计、阻止危险动作、补上下文 |

举一个 TypeScript 项目：

- “优先用现有 service 层，不要新造抽象”适合写 `CLAUDE.md`。
- “Bash 里运行 `rm -rf` 要询问”适合权限规则。
- “每次改 `.ts` 文件后跑 formatter”适合 PostToolUse Hook。
- “任何写 `.env` 的尝试都拦下来”适合 PreToolUse Hook。

这三者不是互相替代。

`CLAUDE.md` 给方向，Permissions 管门槛，Hooks 做检查点。

> [!TIP]
> **一句话理解**：能容忍偶尔遗漏的，写进 `CLAUDE.md`；需要工具级边界的，用 Permissions；到某个事件点必须自动发生的，用 Hooks。

## 3. Hook 其实是生命周期检查点

Hook 的本质是：Claude Code 运行到某个时间点，自动把一段 JSON 输入交给你配置的 handler（处理器）。handler 可以是命令、HTTP 端点、MCP 工具、prompt 或 agent。

先看一个简化时间线：

```mermaid
flowchart LR
    Prompt["用户提交提示"]
    Think["Claude 思考"]
    Pre["PreToolUse<br/>工具执行前"]
    Tool["工具调用<br/>Edit / Write / Bash / MCP"]
    Post["PostToolUse<br/>工具成功后"]
    Stop["Stop<br/>准备停止"]

    Prompt --> Think --> Pre --> Tool --> Post --> Think --> Stop

    style Pre fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style Post fill:#dcfce7,stroke:#22c55e,stroke-width:2px
    style Stop fill:#f3e8ff,stroke:#a855f7,stroke-width:2px
```

Hooks 不是只在“写文件前”能用。官方 Hooks reference 列了很多事件，比如：

- **`UserPromptSubmit`**：用户提示提交时触发，适合注入上下文、拦截危险请求。
- **`PreToolUse`**：工具执行前触发，适合阻止危险命令、改写工具输入。
- **`PermissionRequest`**：即将弹权限请求时触发，适合自动允许/拒绝某类请求。
- **`PostToolUse`**：工具成功后触发，适合格式化、审计、补充反馈。
- **`PostToolUseFailure`**：工具失败后触发，适合记录失败、给 Claude 修复线索。
- **`Stop`**：Claude 准备停下时触发，适合检查任务是否真的完成。
- **`SubagentStop`**：子代理准备结束时触发，适合审查子代理结果。
- **`PreCompact` / `PostCompact`**：上下文压缩前后触发，适合归档或重新注入关键上下文。
- **`Elicitation` / `ElicitationResult`**：MCP server 请求用户输入前后触发，适合审计或自动处理信息征询。

新手不需要一口气记完。先记三个最常用的：

- `PreToolUse`：动手前拦。
- `PostToolUse`：动手后补。
- `Stop`：收工前查。

**Hook 触发顺序到底是什么样**：一次完整工具调用的事件链是 `UserPromptSubmit → (思考) → PreToolUse → (Permission 检查) → 工具执行 → PostToolUse / PostToolUseFailure → (思考) → Stop`。**关键事实**：① PreToolUse 在 Permission 检查**之前**触发——Hook 可以提前拦掉危险操作不弹权限窗；② Stop 在准备结束本轮之前触发——可以阻止 Claude 过早收工。所以 Hook 不是"事后审计"，是**主动嵌入决策链路**——这是它跟"日志"的本质区别。

> [!WARNING]
> **新手坑**：不要为了“控制感”给每个事件都挂 Hook。Hook 越多，运行越慢，干扰越多，排查也越难。

## 4. 一个最小 Hook 长什么样

Hooks 写在 settings JSON 里。结构有三层：

1. 选事件，比如 `PreToolUse`。
2. 选 matcher group，比如只匹配 `Write` 或 `Bash`。
3. 放一个或多个 hook handler。

比如阻止写 `.env`：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.sh"
          }
        ]
      }
    ]
  }
}
```

脚本大概这样：

```bash
#!/usr/bin/env bash
set -euo pipefail

INPUT="$(cat)"
FILE_PATH="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')"

case "$FILE_PATH" in
  *.env|*/.env|*.env.*)
    echo "Blocked: environment files may contain secrets and must be edited manually." >&2
    exit 2
    ;;
esac

exit 0
```

Claude Code 会把事件 JSON 通过 stdin 传给 command hook。上面脚本读 `tool_input.file_path`，如果目标是环境文件，就返回 exit code 2。

> [!IMPORTANT]
> **关键点**：Hook 不需要说服 Claude。它在工具执行前已经拿到了结构化输入，可以直接基于文件路径、命令、权限模式做判断。

## 5. exit code 2 不只是“报错”

官方 Hooks reference 明确说：exit 0 表示成功；exit 2 表示 blocking error；其他退出码对多数事件来说是 non-blocking error。

这很重要：

- `exit 0`：成功，动作继续；stdout 里的 JSON 可能被解析。
- `exit 2`：阻止；stderr 会反馈给 Claude。
- `exit 1` 或其他：多数事件下只是非阻塞错误，动作继续。

所以不要用传统直觉写：

```bash
exit 1
```

如果你想阻止操作，要写：

```bash
echo "Blocked: do not edit .env" >&2
exit 2
```

并且记住一个细节：**exit 2 时，Claude Code 忽略 stdout 和 stdout 里的 JSON，只看 stderr 作为反馈。** 如果你要用 JSON 做更细控制，通常是 exit 0 然后在 stdout 输出 JSON。

例如 PreToolUse 可以返回：

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed."
  }
}
```

> [!TIP]
> **实操判断**：简单阻止用 `stderr + exit 2`；需要更细的 allow / deny / ask / 修改输入，用 `exit 0 + JSON output`。

## 6. 五种 handler：不只有 shell

旧理解里，Hook 常被理解成“自动跑一个 shell 脚本”。现在已经不止这样。

官方 Hooks reference 里的 handler type 有五类：

- **`command`**：执行 shell 命令，适合格式化、文件保护、日志。
- **`http`**：POST 到一个 URL，适合通知、审计、接外部系统。
- **`mcp_tool`**：调已连接 MCP server 的工具，适合复用现有 MCP 能力。
- **`prompt`**：让 Claude 模型做一次判断，适合检查是否真的完成、是否符合复杂规则。
- **`agent`**：启动带工具的 verifier agent，适合需要读文件、搜索后再判断的场景。

这五种放在一条光谱上看：

```text
确定性强 -> 判断力强
command -> http -> mcp_tool -> prompt -> agent
```

例子：

- 改 `.ts` 后跑 Prettier：`command`。
- 每次权限请求发审计系统：`http`。
- 复用一个内部 policy MCP server：`mcp_tool`。
- Stop 前判断“任务是否真的完成”：`prompt`。
- Stop 前让一个 verifier agent 搜索测试结果、读 diff 再判断：`agent`。

> [!CAUTION]
> **不要反过来用重武器**：能用 command 判断的，就不要用 prompt；能用 prompt 判断的，不一定要开 agent。判断越智能，成本和不确定性也越高。

**为什么 Anthropic 设计 5 种 handler 而不是只给 command**：用 shell command 解决一切是最简单——但有些判断 shell 写不出来。"任务真的完成了吗"需要语义理解，正则做不到——这就是 prompt handler 的位置。"任务完成了但代码风格还有 3 处问题"需要读代码 + 跑命令——这就是 agent handler。**5 种递进的设计哲学是**：让你按"判断复杂度"选合适的工具，不强迫所有规则都在 shell 里写——避免出现"写 100 行 shell 模拟模型推理"的反模式。

## 7. Block-at-Write 还是 Block-at-Submit

Hook 很容易被用重。

比如你想保证测试通过。你可以在每次 `Edit` 后立刻跑全量测试吗？技术上可以。但这会让 Claude 每改一个文件就等很久，任务体验会崩。

更好的方式是区分两类规则：

- **Block-at-Write**：动手前或刚动手后立即拦，适合改 `.env`、删库、写生产配置。
- **Block-at-Submit**：收工前统一检查，适合测试、lint、文档完整性、验收清单。

危险动作要早拦，因为一旦发生就可能造成损失。

质量检查可以晚一点，因为 Claude 需要先完成一组相关修改，再统一修 lint、测试和格式。

Stop hook 就适合 Block-at-Submit：

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "复盘本轮结果。如果还缺测试或必要的验证步骤，就阻止停止：$ARGUMENTS"
          }
        ]
      }
    ]
  }
}
```

> [!IMPORTANT]
> **底层逻辑**：早拦保护不可逆风险，晚查保护交付质量。不要把所有质量规则都塞到每一次文件写入前。

## 8. Hook 配置放在哪里

Hook 遵守 Claude Code settings scope：

- **`~/.claude/settings.json`**：作用于你所有项目，适合个人通知、个人日志。
- **`.claude/settings.json`**：作用于当前仓库所有协作者，适合团队规范、安全规则。
- **`.claude/settings.local.json`**：只作用于当前仓库你自己，适合本机实验、临时调试。
- **plugin**：作用于插件启用处，适合插件随带的 Hook。
- **managed settings**：作用于组织级，适合安全和合规强制策略。

官方配置文档里，scope 优先级大致是：managed 最高，其次命令行参数、local、project、user。项目级能覆盖用户级，managed 不能被普通用户覆盖。

你可以用：

```text
/hooks
```

打开只读 hooks 菜单，检查当前有哪些 hook、来自哪个来源、匹配什么事件。官方说明里 `/hooks` 是 read-only：想改配置，还是要编辑 settings JSON 或让 Claude 修改文件。

> [!WARNING]
> **团队规则别放错**：团队必须共同遵守的 Hook 放 `.claude/settings.json`；只对你本机有意义的通知、路径和实验，放 `.claude/settings.local.json` 或用户级。

## 9. 幂等性和性能

Hooks 是代码。代码就有工程质量。

第一条是幂等性。

幂等性就是：跑一次和跑多次，结果不应该越来越坏。

好的 Hook：

```text
格式化文件
检查文件路径
追加带唯一 id 的审计日志
```

危险 Hook：

```text
每触发一次就插一条数据库记录
每触发一次就覆盖一个全局配置
每触发一次就发一条无法去重的外部通知
```

Claude Code 可能多次编辑同一个文件，PostToolUse 就可能多次触发。如果 Hook 没有幂等性，就会制造重复副作用。

第二条是性能。

PreToolUse 是热路径。你在每次工具调用前跑一个 10 秒脚本，Claude Code 体验会立刻变慢。

减少成本的方法：

- 用 `matcher` 限定工具名。
- 用 `if` 进一步限定命令或文件类型。
- 小检查用 command，不要动不动 prompt/agent。
- 长任务考虑 async，但不要用 async 做必须同步拦截的安全规则。

> [!TIP]
> **Hook 设计原则**：越靠前、越高频的事件，Hook 越要短、准、可预测。

## 10. 这一篇和前几篇怎么连起来

到这里，Claude Code 的控制层可以串成一张图。

```mermaid
flowchart TB
    Need["你想控制 Claude Code 行为"]
    Know["长期知识和偏好<br/>CLAUDE.md"]
    Reuse["重复任务流程<br/>Skills"]
    Reach["连接外部系统<br/>MCP"]
    People["隔离或协作<br/>SubAgents / Agent Teams"]
    Guard["确定性检查点<br/>Hooks"]
    Perm["工具访问边界<br/>Permissions"]

    Need --> Know
    Need --> Reuse
    Need --> Reach
    Need --> People
    Need --> Guard
    Need --> Perm

    style Guard fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style Perm fill:#fee2e2,stroke:#ef4444,stroke-width:2px
```

每一层解决的问题不同：

- **`CLAUDE.md`**：让 Claude 知道项目规则，关键词是 should。
- **Skills**：复用任务流程，关键词是 reusable workflow。
- **SubAgents**：隔离旁支任务，关键词是 separate context。
- **Agent Teams**：多会话协作，关键词是 shared task list。
- **MCP**：够到外部系统，关键词是 external tools。
- **Hooks**：到点自动检查，关键词是 deterministic checkpoint。
- **Permissions**：哪些工具能用，关键词是 access control。

第 11 篇会专门讲 Permissions。先记住边界：Hook 可以在事件点做检查，Permissions 决定工具访问规则。两者经常一起用，但不是一回事。

> [!IMPORTANT]
> **一句话理解**：Hooks 是“到点必查”的检查点；Permissions 是“能不能用工具”的访问边界；`CLAUDE.md` 是“希望你怎么做”的说明书。

## 11. 本章自检

试着用自己的话回答：

1. 为什么"我已经写进 CLAUDE.md"不能等同于"这件事一定会发生"？对应 §1-§2。
2. exit 2 和 exit 1 在 Hooks 里有什么关键区别？如果你想阻止写 `.env`，应该用哪个？对应 §5。
3. **动手题** ⭐：在你项目根新建 `.claude/hooks/protect-secrets.sh`，写一个 PreToolUse Hook：当 Claude 想 Edit / Write 任何文件名包含 `.env` / `secret` / `credentials` 的文件时，用 stderr 输出"危险文件不允许编辑"+ exit 2 阻止。然后在 `.claude/settings.json` 注册。**自检**：手动让 Claude 试着改 `.env`，看 Hook 有没有拦下来 + 看 stderr 信息有没有进 Claude 上下文。这一道题做完你会知道：CLAUDE.md 提醒 ≠ 真拦得住，Hook 才是确定性。对应 §1 + §4。

> [!TIP]
> **过关标准**：能用一句话说清：Hooks 是 Claude Code 生命周期里的确定性检查点，用来自动执行、阻止或反馈那些不能只靠模型记忆完成的规则。

<details id="glossary">
<summary><b>本篇术语速查表</b></summary>

- **Hook**：钩子，在 Claude Code 生命周期特定点自动执行的处理器。
- **handler**：处理器，Hook 触发后真正运行的 command、http、prompt 等。
- **matcher**：匹配器，限定 Hook 对哪些工具或事件生效。
- **`PreToolUse`**：工具执行前，工具调用前触发，可阻止危险动作。
- **`PostToolUse`**：工具执行后，工具成功后触发，常用于格式化和审计。
- **`Stop`**：停止前，Claude 准备结束当前回合时触发。
- **exit code**：退出码，脚本结束时返回给 Claude Code 的状态数字。
- **stderr**：标准错误输出，exit 2 时反馈给 Claude 的错误信息来源。
- **idempotency**：幂等性，多次执行结果仍然安全稳定。
- **Permissions**：权限系统，控制 Claude Code 工具访问边界的机制。

</details>

## 官方资料

- [Automate workflows with hooks](https://code.claude.com/docs/en/hooks-guide)
- [Hooks reference](https://code.claude.com/docs/en/hooks)

## 接下来去哪

<Cards>
  <Card href="/docs/claude-code/understanding/11-permissions" title="11 · 权限怎么管" description="Hooks 是检查点，Permissions 是访问边界。下一篇拆 Claude Code 怎么决定某个工具能不能用、要不要问你。" />
  <Card href="/docs/claude-code/understanding/09-mcp" title="09 · 怎么连外部服务（上一篇）" description="MCP 让 Claude Code 够到外部系统。Hooks 帮你在这些外部操作前后加检查和审计。" />
  <Card href="/docs/claude-code/understanding/03-memory" title="03 · 怎么记住你的习惯" description="分不清 CLAUDE.md 和 Hooks 时，回到记忆篇：长期习惯写说明，强制检查写自动化。" />
</Cards>

如果只记一个判断：**凡是“漏一次就会出事”的规则，不要只写成提醒，要放到 Hook 或 Permission 里。**
