<!-- title: 拦截式钩子造自动化安全门禁 -->

> **学习目标**：读完本文，你能用 Claude Code 的 `PreToolUse`/`PostToolUse` 拦截式钩子，搭出一套"危险命令必拦、密钥泄露必挡、写完文件自动格式化、全部操作留痕"的自动化安全门禁，并把它接到 `-p` 批量模式与 `Setup` 钩子上，让无人值守的批量任务也跑在护栏之内。
>
> **适用人群**：已经用过 Claude Code 做过几个项目、想让它在 CI/批处理场景里"撒手跑"却不敢的开发者。假设你了解 `settings.json` 的基本结构，但不熟悉钩子的拦截机制。

## 一、为什么拦截式钩子是自动化的"最后一道闸"

当你在交互模式下用 Claude Code，每一次 `rm -rf`、每一次写入 `.env`、每一次 `git push --force`，你都能在终端里看见并手动放行或拒绝。可一旦切到 `-p` 批量模式——比如让它一夜之间重构 100 个文件——你就失去了这层人眼监督。模型一旦"自作主张"执行了危险命令，可能造成的损失往往要到事后才被发现。

Claude Code 的权限系统（`permissions.allow/deny`）能做静态规则匹配，比如"永远禁止 `Bash(rm -rf:*)`"。但真实项目里的危险操作常常是**动态的**：

- 写入的文件内容里藏着一段 `AKIA...` 形态的 AWS 密钥——静态规则无法预判内容；
- 命令是 `rm -rf $BUILD_DIR`，`$BUILD_DIR` 是否指向 `/` 取决于运行环境；
- 你想在拦截后**给模型一句可读的理由**，引导它换一种安全写法，而不是简单粗暴地拒绝。

这些场景正是**拦截式钩子**（PreToolUse / PostToolUse）的用武之地。与"上下文注入式"钩子（`SessionStart`、`UserPromptSubmit`，用于往对话里塞背景信息）不同，拦截式钩子的核心能力是**在工具执行前/后做判断并返回决策**——既能挡，也能放行，还能在放行后顺手补一刀（格式化、留痕、扫描）。

本文要造的"自动化安全门禁"由四道关卡组成，全部用钩子实现：

| 关卡 | 钩子事件 | 职责 |
|:--|:--|:--|
| 命令护栏 | `PreToolUse` (matcher=Bash) | 拦 `rm -rf /`、`git push -f`、`curl | sh` 等危险命令 |
| 密钥门神 | `PreToolUse` (matcher=Edit\|Write) | 写入前扫描 diff，发现疑似密钥则 deny |
| 自动修复 | `PostToolUse` (matcher=Edit\|Write) | 写完自动跑 prettier/eslint --fix |
| 全量留痕 | `PostToolUse` (matcher=*) | 每次工具调用追加到审计日志 |
| CI 预检 | `Setup` (matcher=maintenance) | `-p --maintenance` 模式下做一次性环境检查 |

## 二、前置条件

- **Claude Code v2.1.191+**（逗号分隔的 matcher 语法需要此版本；`Setup` 钩子需要较新版本）。用 `claude --version` 确认。
- 一个用于测试的 git 仓库，目录里有几个 `.js` 或 `.ts` 文件，已装好 `prettier`、`eslint`（`npm i -D prettier eslint`）。
- 终端里有 `jq`（钩子脚本要用它解析 stdin 上的 JSON）。`jq --version` 确认；没有就 `brew install jq` 或 `apt install jq`。
- 一颗"先读后改"的心：钩子配置写错不会立即报错，而是悄悄不生效，所以每一步都要**实跑验证**。

## 三、钩子的三种节奏：先想清楚在哪一道闸门拦截

在动手前，先建立一个心智模型。Claude Code 把钩子事件按触发频率分成三档，理解这三档是判断"用哪个事件"的前提：

```mermaid
flowchart TD
    A[会话开始] --> B[SessionStart 一次/会话]
    B --> C[你提交一条 prompt]
    C --> D[UserPromptSubmit 一次/轮]
    D --> E[智能体循环 每轮可能多次]
    E --> F{PreToolUse 工具执行前}
    F -->|deny| G[阻断 该工具不执行]
    F -->|放行| H[工具真正执行]
    H --> I[PostToolUse 执行后]
    I --> J[同一轮内继续调用工具?]
    J -->|是| F
    J -->|否| K[Stop 本轮结束]
    K --> L[你下一条 prompt]
    L --> D
    style G fill:#fdd,stroke:#c00
    style H fill:#dfd,stroke:#0a0
```

关键点：**只有 `PreToolUse` 能在工具真正执行前把它拦下来**。`PostToolUse` 是"事后"——文件已经写完了、命令已经跑完了，你能做的是格式化、留痕、告警，但无法阻止已发生的事。所以"挡"的活儿必须交给 `PreToolUse`，"补救"的活儿才交给 `PostToolUse`。这是整条门禁的设计基石。

钩子的配置统一写在 `.claude/settings.json` 的 `hooks` 字段下，三层嵌套：**事件名 → matcher 组 → handler 列表**。下面我们逐关搭建。

## 四、第一关：PreToolUse 命令护栏——拦住 `rm -rf /`

### 操作说明

先建钩子脚本。在项目根目录创建 `.claude/hooks/block-dangerous.sh`：

```bash
#!/usr/bin/env bash
# .claude/hooks/block-dangerous.sh
# 从 stdin 读取钩子输入 JSON，提取 Bash 命令，命中黑名单则 deny
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

# 危险命令黑名单：递归删根、强制推送到主分支、管道执行远程脚本
deny_patterns=(
  'rm[[:space:]]+-rf[[:space:]]+/?($|[[:space:]])'   # rm -rf / 或 rm -rf / ...
  'git[[:space:]]+push.*--force'                       # 强制推送
  'curl[[:space:]].*\|[[:space:]]*sh'                 # curl ... | sh
  '>[[:space:]]*/dev/sda'                              # 直接写裸设备
)

for pat in "${deny_patterns[@]}"; do
  if echo "$COMMAND" | grep -Eq "$pat"; then
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "命令命中安全门禁黑名单: $pat。请改用更安全的等价操作，例如限制作用域或分步确认。"
  }
}
EOF
    exit 0
  fi
done
exit 0   # 静默放行：exit 0 + 无输出 = 不干预，走正常权限流程
```

给它执行权限：`chmod +x .claude/hooks/block-dangerous.sh`。

> **决策返回的两种姿势**：脚本通过 stdout 打印一段 JSON，里面 `permissionDecision` 设为 `"deny"` 即可阻断。`"deny"` 是硬挡（直接拒绝且把 reason 喂给模型）；此外还有 `"allow"`（直接放行，绕过后续权限弹窗）。如果脚本什么都不输出、`exit 0`，表示"我不表态"，工具调用会继续走默认权限流程——**保持沉默不会批准它**。另一个姿势是直接 `exit 2`，这也会阻断，但 reason 只能来自 stderr 文本，不如 JSON 灵活，本文统一用 JSON。

然后在 `.claude/settings.json` 注册它：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *) || Bash(git push *) || Bash(curl *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}
```

注意 `if` 字段。它用权限规则语法对**工具名+参数**一起过滤，比 `matcher`（只匹配工具名）更细。`Bash(rm *)` 的意思是"只有当 Bash 命令的某个子命令以 `rm ` 开头时，这个 handler 才真正 spawn"。

### 预期结果

`if` 的妙处在于**省进程**：模型跑 `npm test` 时，`Bash(rm *)` 不匹配，脚本根本不会启动。只有当命令形如 `rm -rf dist` 时脚本才会被唤起。这是官方文档反复强调的性能要点——`if` 是"最佳努力"过滤器，能过滤掉绝大多数无关调用。

你可以手动喂数据测试脚本逻辑，不必真去触发：

```bash
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
  | .claude/hooks/block-dangerous.sh
```

应输出一段含 `"permissionDecision": "deny"` 的 JSON。当模型在会话里真的想跑 `rm -rf /tmp/build` 时，工具调用会被阻断，模型会收到你写的中文 reason，并据此换一种写法重试。

### 常见问题与解决

- **脚本不生效，模型还是跑掉了危险命令**：八成是 `settings.json` 没被读到。Claude Code 只读取四个位置（`~/.claude/settings.json`、项目 `.claude/settings.json`、`.claude/settings.local.json`、托管策略）。检查文件路径和 JSON 语法（`jq . .claude/settings.json` 能否正常解析）。
- **`if` 语法报错或永远不触发**：`if` 只接受**单条**权限规则，不支持 `&&`/`||` 列表。上面示例里的 `||` 是为了演示——真实配置里要么拆成多个 handler，要么干脆不要 `if`、在脚本里用 grep 兜底（更简单可靠）。这是踩坑高发点：**安全拦截宁可多跑几次脚本，也不要依赖 `if` 做精确判断**。
- **拦截后模型卡住不重试**：`deny` 的 reason 会被当成系统提示喂给模型，只要 reason 写得够清楚（"改用 `rm -rf ./dist` 限定目录"），模型通常会自己纠正。若反复卡住，说明 reason 不够可执行。

## 五、第二关：PreToolUse 密钥门神——写入前扫描内容

静态 deny 规则挡不住"内容里的密钥"，因为权限系统看的是命令/路径，看不到即将写入的字符。这一关用 PreToolUse 拦截 `Edit`/`Write`，把待写内容过一遍密钥正则。

### 操作说明

新建 `.claude/hooks/secret-scan.sh`：

```bash
#!/usr/bin/env bash
# .claude/hooks/secret-scan.sh
# 扫描 Edit/Write 的待写内容，命中常见密钥形态则 deny
set -euo pipefail
INPUT=$(cat)

# Write 的内容在 tool_input.content；Edit 的新内容在 tool_input.new_string
CONTENT=$(echo "$INPUT" | jq -r '
  .tool_input.content // .tool_input.new_string // ""')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // "?"')

# 常见密钥指纹：AWS Access Key、GitHub PAT、Google API Key、私钥头
patterns=(
  'AKIA[0-9A-Z]{16}'                # AWS Access Key ID
  'gh[pousr]_[A-Za-z0-9]{36}'       # GitHub fine-grained / classic PAT
  'AIza[0-9A-Za-z_-]{35}'           # Google API Key
  '-----BEGIN (RSA |EC |)PRIVATE KEY-----'
)

for pat in "${patterns[@]}"; do
  if echo "$CONTENT" | grep -Eq "$pat"; then
    cat <<EOF
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "检测到疑似密钥 ($pat) 将写入 $FILE_PATH。请改用环境变量或密钥管理服务，不要把密钥硬编码进文件。"
  }
}
EOF
    exit 0
  fi
done
exit 0
```

`chmod +x` 后注册：

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

### 预期结果

当模型试图 `Write` 一个含 `AKIAIOSFODNN7EXAMPLE` 的配置文件时，写入会被拦在执行前，模型收到密钥告警。这一关的价值在批量场景里尤其明显：让模型重构 100 个文件时，它会遍历读取并改写，过程中很容易把某个 `.env.example` 的真实密钥"顺手"复制到代码里——门神会在那一刻喊停。

### 常见问题与解决

- **误报太多，正常的长字符串被当成密钥**：调正则。比如 `AKIA[0-9A-Z]{16}` 已经相当精确；若项目里有合法的长 token，可以在脚本里加一个白名单文件 `.claude/secret-allowlist.txt`，命中后跳过。
- **`jq` 解析 `content` 失败**：`Edit` 工具的内容字段是 `new_string`，`Write` 是 `content`，`MultiEdit` 是 `edits` 数组。脚本里用 `//` 提供 fallback，但 MultiEdit 要单独处理（遍历 `.edits[].new_string`）。
- **密钥已被模型读取进上下文，拦写入没用**：门神只挡"写出去"，挡不住"已被读到"。对真正敏感的目录，配合 `permissions.deny` 里的 `Read(.env)` 从源头禁止读取，双管齐下。

## 六、第三关：PostToolUse 自动修复——写完即格式化

"挡"是防守，"修"是进攻。模型写完文件后，代码风格未必符合团队规范。用 PostToolUse 在写入成功后自动跑格式化，既省了模型一次工具调用，又保证落盘的代码永远是干净的。

### 操作说明

新建 `.claude/hooks/auto-format.sh`：

```bash
#!/usr/bin/env bash
# .claude/hooks/auto-format.sh
# 写入成功后，对目标文件跑 prettier（若项目里有）
set -euo pipefail
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

[ -z "$FILE_PATH" ] && exit 0
[ -f "$FILE_PATH" ] || exit 0

# 只处理 JS/TS，且项目装了 prettier 才跑
case "$FILE_PATH" in
  *.js|*.ts|*.jsx|*.tsx|*.json|*.css|*.md)
    if [ -x "node_modules/.bin/prettier" ]; then
      node_modules/.bin/prettier --write "$FILE_PATH" >/dev/null 2>&1 || true
    fi
    ;;
esac
exit 0
```

注册到 `PostToolUse`：

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-format.sh"
          }
        ]
      }
    ]
  }
}
```

### 预期结果与注意

每次模型写完一个 `.js` 文件，prettier 立刻在该文件上跑一次。模型下次读这个文件时看到的就是格式化后的版本，避免"格式漂移"累积。

**关键陷阱**：PostToolUse 的脚本如果输出到 stderr 或非零退出，可能会把错误信息当成"工具执行异常"反馈给模型，导致模型困惑地"修复"一个根本不存在的问题。所以脚本结尾务必 `exit 0`，内部命令加 `|| true` 吞掉错误。**PostToolUse 永远不要 deny**——它没有 `permissionDecision` 这回事，工具已经执行完了。

## 七、第四关：全量留痕——PostToolUse 审计日志

合规场景要求"模型动过什么，全记下来"。一个 matcher 为 `*` 的 PostToolUse 钩子，能把每次工具调用追加进日志文件。因为"记日志"不影响主流程，这里正好用上 `async` 字段——后台运行，不阻塞智能体循环。

### 操作说明

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "async": true,
            "command": "jq -c '{ts: now|todateiso8601, tool: .tool_name, input: .tool_input}' >> ${CLAUDE_PROJECT_DIR}/.claude/audit.jsonl"
          }
        ]
      }
    ]
  }
}
```

### 预期结果

`.claude/audit.jsonl` 会逐行累积形如 `{"ts":"2026-07-01T08:14:22Z","tool":"Edit","input":{...}}` 的记录。`async: true` 让写日志在后台进行，模型不会停下来等磁盘 I/O。把 `audit.jsonl` 加进 `.gitignore`，避免把审计日志本身提交进仓库。

> **`async` vs `asyncRewake`**：普通 `async` 是"发了就忘"，钩子结果不影响模型。`asyncRewake: true` 则在钩子以退出码 2 结束时**唤醒模型**，把 stderr 当系统提醒塞回去——适合"后台长任务失败时让模型知道"的场景，但写审计日志用不到。

## 八、第五关：`Setup` 钩子——给 `-p` 批量任务做一次性预检

前三关是"运行中"的护栏。但批量任务开始前，你还想做一次环境体检：依赖装好了吗？`.env` 没被误提交吗？测试能跑通吗？`Setup` 事件专为此设计——它只在 `claude --init-only` 或 `-p` 模式带 `--init`/`--maintenance` 时触发，做一次性的 CI/脚本准备。

### 操作说明

新建 `.claude/hooks/preflight.sh`：

```bash
#!/usr/bin/env bash
# .claude/hooks/preflight.sh
# 批量任务启动前的一次性体检
set -uo pipefail
fail=0

echo "[preflight] 检查依赖..." >&2
[ -d node_modules ] || { echo "✗ node_modules 缺失，先 npm ci" >&2; fail=1; }

echo "[preflight] 检查密钥是否误提交..." >&2
if git ls-files | grep -Eq '\.env$'; then
  echo "✗ .env 已被 git 跟踪，立即处理" >&2; fail=1
fi

echo "[preflight] 检查测试基线..." >&2
if ! npm test --silent >/dev/null 2>&1; then
  echo "✗ 测试基线未通过，先修红再开工" >&2; fail=1
fi

[ $fail -eq 0 ] && echo "[preflight] 通过" >&2
exit 0
```

注册：

```json
{
  "hooks": {
    "Setup": [
      {
        "matcher": "maintenance",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/preflight.sh" }
        ]
      }
    ]
  }
}
```

### 预期结果

```bash
claude -p --maintenance "检查环境" 2>&1 | grep preflight
```

会看到三步体检的输出。`--maintenance` 模式下 Claude Code 跑完 `Setup` 钩子就做收尾，不进入完整智能体循环——非常适合"只体检不干活"的 CI 步骤。体检不过时，脚本的 stderr 会出现在日志里，CI 据此决定是否继续。

## 九、把五道关拼成一条 `-p` 批量流水线

现在把所有钩子合并进同一个 `settings.json`（同名事件下可以放多个 matcher 组），再写一个批量驱动脚本。完整配置：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh" }
        ]
      },
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/secret-scan.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-format.sh" }
        ]
      },
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "async": true,
            "command": "jq -c '{ts: now|todateiso8601, tool: .tool_name, input: .tool_input}' >> ${CLAUDE_PROJECT_DIR}/.claude/audit.jsonl" }
        ]
      }
    ],
    "Setup": [
      {
        "matcher": "maintenance",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/preflight.sh" }
        ]
      }
    ]
  }
}
```

驱动脚本 `.claude/run-batch.sh`：先体检，再以 `stream-json` 输出跑批量任务，最后扫审计日志统计拦截次数。

```bash
#!/usr/bin/env bash
# .claude/run-batch.sh <prompt-file>
set -euo pipefail
PROMPT_FILE="${1:?用法: run-batch.sh <prompt-file>}"

echo "=== 1. 预检 ===" 
claude -p --maintenance "preflight" || { echo "预检失败，中止"; exit 1; }

echo "=== 2. 批量执行（护栏已激活）==="
claude -p "$(cat "$PROMPT_FILE")" \
      --output-format stream-json \
      --verbose \
  | tee .claude/run-stream.jsonl

echo "=== 3. 拦截统计 ==="
grep -c '"deny"' .claude/audit.jsonl 2>/dev/null \
  && echo "次危险操作被门禁拦截" \
  || echo "本次无拦截"
```

`--output-format stream-json` 把每个工具调用、每次拦截都吐成结构化 JSON 行，方便事后用 `jq` 复盘模型在批量任务里到底做了什么、被挡了几次。这就是一条"预检 → 护栏内批量 → 审计复盘"的完整闭环。

## 十、Mermaid：整条门禁的执行流

```mermaid
sequenceDiagram
    autonumber
    participant U as 你的脚本 run-batch.sh
    participant CC as Claude Code (-p)
    participant H as 钩子处理器
    participant FS as 文件系统

    U->>CC: claude -p --maintenance (预检)
    CC->>H: Setup 事件 → preflight.sh
    H-->>CC: 通过 / 失败
    alt 预检失败
        CC-->>U: 中止
    else 预检通过
        U->>CC: claude -p "批量重构" --output-format stream-json
        loop 每个工具调用
            CC->>H: PreToolUse (Bash?) → block-dangerous.sh
            alt 命中黑名单
                H-->>CC: deny + 中文理由
                CC->>CC: 模型据理由换写法
            else 放行
                H-->>CC: 静默 (exit 0)
                CC->>H: PreToolUse (Edit?) → secret-scan.sh
                alt 命中密钥
                    H-->>CC: deny
                else 放行
                    CC->>FS: 真正执行写入
                    CC->>H: PostToolUse → auto-format.sh
                    H->>FS: prettier --write
                    H-->>CC: (async) 审计日志追加
                end
            end
        end
        CC-->>U: stream-json 流（含每次拦截）
        U->>U: grep deny 统计拦截次数
    end
```

## 十一、常见问题汇总

- **钩子配置改了不生效**：Claude Code 在会话启动时加载钩子。交互模式下改完 `settings.json` 要重开会话；`-p` 模式每次都是新进程，无需担心。可用 `claude --debug hooks`（若版本支持）观察钩子是否被加载与触发。
- **拦截太狠，正常工作流被拖慢**：先排查是不是 matcher 太宽（`*` 会匹配所有工具）。把审计日志这类"全量"钩子设 `async: true`；把格式化这类"按需"钩子用精确 matcher（`Edit|Write|MultiEdit`）。
- **`if` 字段没按预期过滤**：记住 `if` 是"最佳努力"，遇到无法解析的 Bash 命令会**fail open**（照样跑钩子）。所以 `if` 用来**省进程**可以，用来**做安全判定**不行——安全判定必须在脚本内部用 grep/正则兜死。官方文档原话：硬性的允许/拒绝要交给权限系统，钩子做不了百分百保证。
- **Windows 上跑不通**：命令钩子默认用 `sh -c`（Git Bash）。`.sh` 脚本在原生 PowerShell 环境跑不了，需在 handler 里加 `"shell": "powershell"` 并改写脚本，或确保装了 Git Bash。exec form（带 `args`）能避免 shell 差异，建议优先用 `"command": "node", "args": ["..."]` 跑跨平台脚本。
- **审计日志膨胀**：`.jsonl` 会一直长。定期 `logrotate`，或在脚本里加"超过 N MB 就轮转"的逻辑。`async` 钩子即便慢也不会拖累主流程，但磁盘满了仍会出问题。

## 十二、训练营适用性评估

| 维度 | 评估 |
|:--|:--|
| 适合的训练营类型 | **CI/CD 自动化营、DevSecOps 营、AI 工程化营**。本教程的核心收益是"让 AI 无人值守也安全"，正是这些方向的关键能力。 |
| 不太适合 | 纯入门营。需要先理解权限系统与会话生命周期，零基础建议先学"权限配置入门"。 |
| 预计教学时长 | **3–4 小时**。其中 1 小时讲钩子三种节奏与决策返回机制，2 小时动手搭五道关，0.5–1 小时跑批量流水线并复盘审计日志。 |
| 学员产出 | 一个可提交进仓库的 `.claude/` 目录（settings.json + 5 个钩子脚本 + run-batch.sh），能在自己的项目里直接复用。 |
| 进阶延伸 | 学完可接 `prompt`/`agent` 类型钩子（让另一个 Claude 实例做内容审查），或把 HTTP 钩子接到企业 SIEM 做集中式审计。 |

## 参考来源

1. [Hooks reference — Claude Code Docs](https://code.claude.com/docs/en/hooks) — 官方钩子完整参考：事件清单、JSON 输入输出格式、`permissionDecision` 决策机制、`if`/`matcher` 语法、async/Setup 等，本文所有技术细节的权威出处。
2. [Automate actions with hooks — Claude Code Docs](https://code.claude.com/docs/en/hooks-guide) — 官方快速上手指南，含从零配置第一个钩子的分步示例。
3. [Claude Code Hooks: PreToolUse & PostToolUse Tutorial — paul-schick.com](https://paul-schick.com/posts/claude-code-hooks-pretooluse-posttooluse/) — 实战向博客，演示用 PreToolUse 拦 `rm -rf`、保护 `.env`、用 PostToolUse 接 Prettier 自动格式化。
4. [Claude Code Hooks (2026): Block Claude Reading .env + 30 Hook Events — morphllm.com](https://www.morphllm.com/claude-code-hooks) — 2026 年版钩子全景，列出 30+ 钩子事件与"用 exit code 2 拦截 .env 读取"等防护配方。
5. [Claude Code Hooks: 15 Automations Every Developer Should Set Up — getbeam.dev](https://getbeam.dev/blog/claude-code-hooks-automation-guide.html) — 15 个开箱即用的 PreToolUse/PostToolUse 自动化配置，可直接作为门禁配方库。

> [!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)
