---
title: "使用 Hooks"
description: "Claude Code Hooks 在生命周期事件上执行确定性自动化。学会事件、matcher、if、exit code、JSON 输出、command/prompt/agent/HTTP hooks 和安全边界。"
canonical: "https://xiangyugongzuoliu.com/tag/claude-code"
verifiedAt: "2026-05-05"
---

Hook 是把“希望 Claude 记得做”变成“事件发生时一定运行”。凡是必须发生、可脚本化、可判定的动作，都不应该只写在 prompt 里。——Aaron

> [!NOTE]
> **这一章用 16 分钟换什么**：前面讲了 Skills 和 Subagents。Skills 给 Claude 方法，Subagents 做隔离任务，Hooks 则在 Claude Code 生命周期上做确定性自动化。读完你应该能配置通知、格式化、阻断、审计、环境重载、permission 自动处理，并知道什么时候该用 command、prompt、agent 或 HTTP hook。

## 1. Hook 解决什么问题

Hook 是在 Claude Code 生命周期事件上自动执行的动作。

典型用途：

- Claude 等待输入时发通知。
- Edit / Write 后自动格式化。
- PreToolUse 阻止危险命令。
- 禁止修改 `.env`、`.git/`、lockfile 等受保护文件。
- compaction 后重新注入关键上下文。
- 配置文件变化时审计。
- 目录变化时重载 `direnv`、devbox、nix 环境。
- PermissionRequest 出现时自动处理特定低风险请求。
- Subagent 开始和结束时做 setup / cleanup。

Hook 和 prompt 的区别：

- Prompt 是让 Claude 记住并判断。
- Hook 是事件发生时直接运行。

> [!IMPORTANT]
> **第一性原理**：需要推理和自由裁量，用 Skill 或 Subagent；需要每次固定执行，用 Hook；需要安全硬边界，优先 permissions + Hook。

```mermaid
flowchart TD
    Rule["一条规则或动作"]
    MustRun["必须每次发生？"]
    NeedsLLM["需要模型判断？"]
    NeedsTools["需要读文件或跑命令判断？"]
    External["需要通知外部系统？"]

    Prompt["CLAUDE.md / Skill"]
    CommandHook["Command Hook"]
    PromptHook["Prompt Hook"]
    AgentHook["Agent Hook"]
    HttpHook["HTTP Hook"]

    Rule --> MustRun
    MustRun -->|否| Prompt
    MustRun -->|是| NeedsLLM
    NeedsLLM -->|否| External
    External -->|是| HttpHook
    External -->|否| CommandHook
    NeedsLLM -->|是| NeedsTools
    NeedsTools -->|否| PromptHook
    NeedsTools -->|是| AgentHook

    style CommandHook fill:#dcfce7,stroke:#22c55e,stroke-width:2px
    style PromptHook fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
    style AgentHook fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style HttpHook fill:#fee2e2,stroke:#ef4444,stroke-width:2px
```

## 2. Hook 和 Skill 的边界

Skill 适合：

- 发布 checklist。
- 代码审查流程。
- API 风格指南。
- 调试 playbook。
- 需要 Claude 推理、取舍、适配上下文的工作方法。

Hook 适合：

- 每次编辑后跑 formatter。
- 每次 Bash 前检查命令。
- 每次会话结束写日志。
- 每次 permission prompt 出现时处理某类低风险请求。
- 每次配置文件变化时审计。

错误用法：

- “不要改 `.env`”只写在 Skill 里。
- “每次保存后格式化”只写在 `CLAUDE.md` 里。
- “发布前一定跑测试”只靠 Claude 自觉。

正确做法：

- 知识和流程写 Skill。
- 确定动作写 Hook。
- 安全拒绝写 permissions deny，必要时再加 Hook 给反馈。

> [!WARNING]
> **Hook 不是更强提示词**：它是自动化执行点。不要把需要人类判断的危险动作做成无确认 Hook。

## 3. Hook 配在哪里

Hook 写在 settings 里：

- User：`~/.claude/settings.json`
- Project：`.claude/settings.json`
- Local：`.claude/settings.local.json`
- Managed：组织级 managed settings
- Plugin：plugin 里的 hook 配置
- Subagent frontmatter：只在该 subagent 生命周期内生效

基础结构：

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}
```

同一个 `hooks` object 里可以有多个事件。不要新增一个 `hooks` key 把旧配置覆盖掉。

查看入口：

```text
/hooks
```

官方说明 `/hooks` 是只读浏览器。新增、修改、删除仍然要编辑 settings JSON，或让 Claude 帮你改配置文件。

> [!TIP]
> **Project Hook 适合团队规则**；User Hook 适合个人通知和日志；Local Hook 适合本机路径；Managed Hook 适合组织强制自动化。

## 4. 第一个 Hook：等待输入时发通知

macOS 示例：

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
```

验证：

1. 写入 `~/.claude/settings.json`。
2. 运行 `/hooks`，确认 `Notification` 下有配置。
3. 触发一个 permission prompt 或让 Claude 完成任务等待输入。

macOS 如果没有通知，先在 Terminal 跑一次：

```bash
osascript -e 'display notification "test"'
```

然后去 System Settings 里给 Script Editor 开通知权限。

> [!TIP]
> **Notification 的空 matcher 表示全部通知类型**。只想匹配权限提示时，用 `permission_prompt`。

## 5. 常见事件：先记这几类

Hook 事件很多，新手先记这几类。

- `SessionStart`：会话开始或恢复。
- `UserPromptSubmit`：用户 prompt 提交后、Claude 处理前。
- `PreToolUse`：工具执行前，可以阻断。
- `PermissionRequest`：权限弹窗即将出现。
- `PermissionDenied`：auto mode 分类器拒绝工具调用。
- `PostToolUse`：工具成功执行后。
- `PostToolUseFailure`：工具失败后。
- `PostToolBatch`：一批并行工具调用完成后。
- `Notification`：Claude Code 发送通知时。
- `SubagentStart` / `SubagentStop`：subagent 开始和结束。
- `TaskCreated` / `TaskCompleted`：任务创建和完成。
- `Stop`：Claude 完成响应。
- `StopFailure`：本轮因 API 错误结束。
- `ConfigChange`：配置文件变化。
- `CwdChanged`：工作目录变化。
- `FileChanged`：被监听文件变化。
- `WorktreeCreate` / `WorktreeRemove`：worktree 创建和移除。
- `PreCompact` / `PostCompact`：compaction 前后。
- `InstructionsLoaded`：`CLAUDE.md` 或 rules 加载到上下文。
- `Elicitation` / `ElicitationResult`：MCP elicitation 相关事件。

> [!IMPORTANT]
> **事件决定能力边界**：阻断工具要用 `PreToolUse`；工具完成后格式化用 `PostToolUse`；权限弹窗自动处理用 `PermissionRequest`；上下文补充用 `SessionStart` 或 `PostCompact`。

## 6. Hook 怎么收到输入

Command hook 会从 stdin 收到 JSON。

常见字段包括：

- `hook_event_name`
- `session_id`
- `transcript_path`
- `cwd`
- `tool_name`
- `tool_input`
- 事件专属字段

例如 `PreToolUse` / `PostToolUse` 里，通常会有：

```json
{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git status"
  }
}
```

脚本里一般用 `jq` 解析：

```bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
```

> [!TIP]
> **先保存输入样本**：写复杂 Hook 前，先把 stdin JSON append 到临时日志，确认字段名和事件结构。

## 7. Exit code 语义

Command hook 通过 exit code 和 stdout / stderr 告诉 Claude Code 下一步怎么做。

- Exit `0`：继续。对 `UserPromptSubmit`、`UserPromptExpansion`、`SessionStart` 等事件，stdout 会加入 Claude 上下文。
- Exit `2`：尝试阻断。stderr 会反馈给 Claude 或用户。对部分事件不能阻断，只会显示错误后继续。
- 其他 exit code：非阻断错误。流程继续，stderr 第一行显示在 transcript，完整 stderr 写 debug log。

例子：阻止 Bash 里出现危险 SQL。

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

input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

if echo "$command" | rg -i "drop table|delete from|truncate table" >/dev/null; then
  echo "Blocked: destructive SQL is not allowed" >&2
  exit 2
fi

exit 0
```

> [!WARNING]
> **不要混用 exit 2 和 JSON allow/deny**：官方说明 exit 2 时 JSON 会被忽略。要结构化控制，用 exit 0 + stdout JSON。

## 8. Structured JSON 输出

Exit code 只能粗略 allow / block。需要更细控制时，exit `0` 并向 stdout 输出 JSON。

`PreToolUse` 可以返回：

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for repository search."
  }
}
```

效果：

- 工具调用被取消。
- `permissionDecisionReason` 会反馈给 Claude。
- Claude 可以调整做法后继续。

`PermissionRequest` 可以返回 allow：

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow"
    }
  }
}
```

也可以更新当前 session 的权限模式：

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedPermissions": [
        { "type": "setMode", "mode": "acceptEdits", "destination": "session" }
      ]
    }
  }
}
```

> [!CAUTION]
> **自动 allow 要极窄**：`PermissionRequest` matcher 不要写空或 `.*`，否则可能自动批准文件写入、shell 命令和外部工具。

## 9. matcher 怎么工作

`matcher` 用来筛选某类事件。

不同事件的 matcher 匹配对象不同：

- `PreToolUse` / `PostToolUse`：工具名，例如 `Bash`、`Edit`、`Write`、`mcp__github__.*`。
- `Notification`：通知类型，例如 `permission_prompt`、`idle_prompt`。
- `SubagentStart` / `SubagentStop`：agent type，例如 `Explore`、`Plan`、custom agent name。
- `PreCompact` / `PostCompact`：`manual` 或 `auto`。
- `ConfigChange`：`user_settings`、`project_settings`、`local_settings`、`policy_settings`、`skills`。
- `FileChanged`：要监听的 literal filenames，例如 `.envrc|.env`。
- `SessionEnd`：结束原因，例如 `clear`。

一些事件不支持 matcher，会每次触发：

- `UserPromptSubmit`
- `PostToolBatch`
- `Stop`
- `TaskCreated`
- `TaskCompleted`
- `CwdChanged`
- `WorktreeCreate`
- `WorktreeRemove`

> [!TIP]
> **先用 matcher 粗筛**：例如 `Edit|Write`。再用 `if` 或脚本内部逻辑做细筛。

## 10. `if` 字段：按工具名和参数过滤

`if` 字段要求 Claude Code v2.1.85 或更高。旧版本会忽略它，导致 hook 对所有 matcher 命中的调用运行。

`if` 使用和 permissions 相同的规则语法。

例子：只检查 git 命令，而不是所有 Bash。

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}
```

`if` 只适用于工具类事件：

- `PreToolUse`
- `PostToolUse`
- `PostToolUseFailure`
- `PermissionRequest`
- `PermissionDenied`

把 `if` 放到其他事件上，会阻止 hook 运行。

> [!IMPORTANT]
> **`matcher` 按事件对象过滤，`if` 按工具和参数过滤**。需要按 Bash 子命令、Edit 路径、MCP tool 细分时，用 `if`。

## 11. PostToolUse：编辑后自动格式化

项目级格式化适合放 `.claude/settings.json`。

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}
```

这个 Hook 的执行逻辑：

1. Claude 用 `Edit` 或 `Write` 改文件。
2. Hook 从 JSON 里取 `.tool_input.file_path`。
3. 把文件路径传给 Prettier。

边界：

- 如果项目不用 Prettier，不要套用这个例子。
- Python、Go、Rust 等项目应该替换成对应 formatter。
- 格式化失败不要静默吞掉，要让 Claude 看到错误。

> [!TIP]
> **格式化适合 PostToolUse**：不要让 Claude 自己记住“改完跑 formatter”。让 Hook 负责。

## 12. PreToolUse：阻止受保护文件

脚本示例：`.claude/hooks/protect-files.sh`

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

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

case "$file_path" in
  *.env|*.env.*|*/.git/*|*package-lock.json)
    echo "Blocked: protected file path $file_path" >&2
    exit 2
    ;;
esac

exit 0
```

注册：

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

脚本要可执行：

```bash
chmod +x .claude/hooks/protect-files.sh
```

> [!IMPORTANT]
> **保护敏感文件优先用 permissions deny**。Hook 的价值是给更具体的反馈和处理复杂条件。

## 13. SessionStart / PostCompact：注入上下文

`SessionStart` 可以在会话开始、恢复、clear 或 compact 后注入上下文。

示例：compaction 后提醒关键规则。

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use pnpm, run pnpm test before committing, and do not edit generated files.'"
          }
        ]
      }
    ]
  }
}
```

注意：

- 每次都要知道的稳定规则，优先写 `CLAUDE.md`。
- Hook 注入适合动态信息，例如最近 commit、当前 sprint、环境状态。
- 不要注入大段内容，避免上下文污染。

> [!TIP]
> **静态规则进 `CLAUDE.md`，动态上下文用 Hook**。

## 14. ConfigChange：审计配置变化

`ConfigChange` 在配置文件被外部进程或编辑器修改时触发。

示例：记录配置变化。

```json
{
  "hooks": {
    "ConfigChange": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
          }
        ]
      }
    ]
  }
}
```

`matcher` 可以过滤：

- `user_settings`
- `project_settings`
- `local_settings`
- `policy_settings`
- `skills`

要阻止变化生效，可以 exit `2`，或返回 structured JSON block。

> [!WARNING]
> **审计日志不要写敏感值**：只记录文件、来源、时间和动作，避免把 token、路径隐私和账号写进日志。

## 15. CwdChanged / FileChanged：重载环境

有些项目依赖 `direnv`、devbox、nix，根据目录或文件变化加载环境。

`SessionStart` + `CwdChanged` 示例：

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ],
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}
```

监听特定文件：

```json
{
  "hooks": {
    "FileChanged": [
      {
        "matcher": ".envrc|.env",
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}
```

`FileChanged` 的 matcher 是 literal filenames 列表，不是正则。

> [!CAUTION]
> **加载 `.env` 要小心**：不要把敏感值打印到 stdout。环境注入写 `CLAUDE_ENV_FILE`，不要回显到对话。

## 16. PermissionRequest：自动批准要极窄

自动批准某个低风险权限请求可以用 `PermissionRequest`。

示例：只自动批准 `ExitPlanMode`。

```json
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PermissionRequest\",\"decision\":{\"behavior\":\"allow\"}}}'"
          }
        ]
      }
    ]
  }
}
```

不要这样做：

- 空 matcher 自动批准所有请求。
- `.*` 自动批准所有请求。
- 对 `Bash`、`Edit`、MCP 写操作做无条件 allow。

> [!IMPORTANT]
> **PermissionRequest Hook 是高风险能力**：只自动处理极小、明确、低风险的权限项。其他保持 ask。

## 17. PermissionDenied：让 auto mode 重试

`PermissionDenied` 在 auto mode 分类器拒绝工具调用时触发。

某些情况下，你可以返回：

```json
{
  "retry": true
}
```

告诉模型这次可以重试。

适合：

- 分类器误拒绝了低风险操作。
- 你有额外脚本确认这次行为安全。

不适合：

- 用来绕过真实风险。
- 对高风险 Bash / Edit / MCP 写操作统一 retry。

> [!WARNING]
> **retry 不是 allow all**：它只是让模型可重试被 auto mode 拒绝的调用。仍要窄匹配、可解释。

## 18. Prompt-based hooks

Prompt hook 用 LLM 做判断，适合输入数据本身就足够判断的场景。

示例：Stop 时检查任务是否完成。

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if all requested tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
          }
        ]
      }
    ]
  }
}
```

输出协议：

- `{"ok": true}`：继续结束。
- `{"ok": false, "reason": "..."}`：根据事件执行阻断或反馈。

适合：

- 检查回答是否覆盖用户请求。
- 判断 summary 是否缺关键信息。
- 基于 hook input 做轻量评估。

不适合：

- 需要读文件、跑测试、查日志。
- 生产级确定性规则。

> [!TIP]
> **Prompt hook 是判断，不是执行**：需要实际查代码状态时用 agent hook 或 command hook。

## 19. Agent-based hooks

Agent hook 会 spawn subagent。官方标注为 experimental，生产工作流优先用 command hooks。

适合：

- 需要读文件。
- 需要搜索代码。
- 需要跑命令验证。
- 单次 prompt 判断不够。

示例：Stop 前检查测试是否通过。

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
```

边界：

- 默认 timeout 比 prompt hook 长。
- 最多会使用一定数量的 tool-use turns。
- 更贵、更慢、更复杂。

> [!WARNING]
> **Agent hook 不适合替代 CI**：它可以做本地预检，但真正发布前仍要依赖可重复的测试和 CI。

## 20. HTTP hooks

HTTP hook 适合把事件发送到外部系统：

- Slack。
- Discord。
- 内部 webhook。
- 审计平台。
- 任务系统。
- 监控系统。

适合做通知和记录，不适合直接做高风险生产操作。

安全要点：

- 使用 HTTPS。
- token 放在环境变量或凭据系统。
- 不要把完整 prompt、完整 diff、密钥、路径隐私发出去。
- 给外部系统加最小权限。
- 设置合理 timeout。

> [!CAUTION]
> **HTTP hook 是数据外发通道**：默认只发送必要字段。不要把 transcript 或 tool input 原样推到第三方。

## 21. Async hooks

Async hook 适合不需要阻塞 Claude 的后台动作。

适合：

- 发送通知。
- 写审计日志。
- 上传指标。
- 异步触发慢任务。

不适合：

- 阻止工具调用。
- 决定是否继续。
- 必须在下一步前完成的检查。

> [!TIP]
> **阻断用同步，旁路用 async**：需要影响当前流程就不要异步。

## 22. Subagent hooks

Subagent 可以在自己的 frontmatter 里定义 hooks。

示例：

```markdown
---
name: db-reader
description: Execute read-only database queries.
tools: Bash
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/validate-readonly-query.sh"
---

You are a database analyst with read-only access.
Only execute SELECT queries.
```

Subagent hooks 的特点：

- 只在这个 subagent 生命周期内生效。
- agent 作为 subagent 或通过 `--agent` 作为 main session 时都会运行。
- plugin-shipped agents 不支持 `hooks`、`mcpServers`、`permissionMode` 这几个字段。

Project settings 也有：

- `SubagentStart`
- `SubagentStop`

适合做 setup / cleanup。

> [!IMPORTANT]
> **Subagent 工具边界粗，Hook 边界细**：例如只允许 SELECT，要用 `PreToolUse` 校验 Bash 内容。

## 23. Hooks 和 permission modes

Hooks 与 permission modes 是两层不同机制。

- `PreToolUse` 在工具调用前触发，可以阻断。
- `PermissionRequest` 在权限弹窗前触发，可以自动回答某些请求。
- `PermissionDenied` 可以处理 auto mode 拒绝。
- `bypassPermissions` 仍然会影响权限提示出现与否。

不要假设 Hook 可以解决所有模式差异。尤其是：

- auto mode 下部分请求可能先被分类器拒绝。
- background subagents 预批准后会 auto-deny 未批准项。
- managed settings 可能禁用 bypass 或强制策略。

> [!WARNING]
> **Hook 不是权限系统替代品**：权限边界写 permissions；Hook 用来补自动化、反馈、审计和复杂条件判断。

## 24. Hook 合并和执行模型

官方说明：事件触发时，所有匹配 hooks 会并行运行。相同命令会自动去重。

这带来几个后果：

- 多个 scope 的 hooks 可能都会执行。
- 不要假设 hooks 按顺序串行。
- 如果一个 hook 依赖另一个 hook 的输出，设计就不稳。
- 要串行流程，就写成一个脚本内部步骤。
- Hook 输出越多，越可能污染上下文。

> [!TIP]
> **Hook 要独立幂等**：同一事件下每个 Hook 都应该能独立运行，多次运行也不破坏状态。

## 25. 安全边界

Hook 是自动运行命令，所以风险很真实。

默认原则：

- 不要在 Hook 里删除文件。
- 不要自动发布生产。
- 不要自动 `git push`。
- 不要把密钥写到日志。
- 不要把完整 transcript 发给外部服务。
- 不要对权限请求做宽 matcher allow。
- 不要执行仓库里不可信脚本，除非已信任 workspace。
- Project Hook 进 git 前要 review。
- Managed Hook 应由管理员维护。

脚本建议：

- shell 脚本首行使用 shebang。
- 使用 `set -euo pipefail`。
- 用 `jq` 解析 JSON，避免字符串硬拆。
- 对路径做白名单或严格匹配。
- 出错时 stderr 写明确原因。
- 对网络调用设置 timeout。

> [!CAUTION]
> **Hook 的能力取决于本机权限**：Claude Code 能运行什么，Hook 就可能运行什么。不要把不可信 Hook 当普通提示词看待。

## 26. 常见故障：Hook 不触发

按这个顺序查：

1. 运行 `/hooks`，确认事件下有配置。
2. 检查写入的是正确 settings scope。
3. 确认 JSON 没有被另一个 `hooks` key 覆盖。
4. 检查 event name 拼写。
5. 检查 matcher 是否匹配事件对象。
6. 如果用了 `if`，确认 Claude Code 版本至少 v2.1.85。
7. 确认脚本有执行权限。
8. 检查脚本路径，优先用 `$CLAUDE_PROJECT_DIR`。
9. 打开 verbose / debug log 看 stderr。

> [!TIP]
> **先写最小 echo hook**：确认事件能触发，再逐步加 matcher、if、脚本逻辑。

## 27. 常见故障：Hook 报 JSON validation failed

常见原因：

- stdout 输出了非 JSON 内容，但事件期待 JSON。
- prompt hook 返回的不是 `{"ok": true}` 或 `{"ok": false, "reason": "..."}`。
- command hook 同时写了 JSON 和普通日志到 stdout。
- exit code 2 时还期待 stdout JSON 生效。
- JSON 引号被 shell 转义破坏。

修法：

- 普通日志写 stderr。
- stdout 只写机器要读的 JSON。
- 用 `jq -n` 生成 JSON，减少转义错误。
- 需要阻断就 exit 2 + stderr，不要混 JSON。

> [!WARNING]
> **stdout 是协议通道**：不要随便 `echo debug` 到 stdout。调试日志写 stderr 或文件。

## 28. 常见故障：Stop hook 无限循环

Stop hook 如果一直返回 `ok: false`，Claude 会继续工作，然后再次触发 Stop。

常见原因：

- 判定条件过于理想化。
- reason 不可执行。
- Claude 无法满足 Hook 要求。
- Hook 没有最大尝试次数。

修法：

- reason 写具体下一步。
- 增加外部状态计数。
- 只检查本轮用户明确要求。
- 复杂验证交给 CI，不要用 Stop hook 无限追求完美。

> [!CAUTION]
> **Stop hook 要能收敛**：它应该推动完成，不应该把会话锁进无法满足的循环。

## 29. 常见故障：Hook 影响太大

表现：

- 所有 Bash 都被检查，速度变慢。
- 所有权限都被自动批准。
- 每次编辑都跑整个测试套件。
- 大量 stdout 被塞进上下文。
- 多个 Hook 并行写同一个文件导致日志乱序。

处理：

- 缩小 matcher。
- 加 `if`。
- 用文件路径筛选。
- 把慢任务改 async 或 CI。
- stdout 限制在必要内容。
- 日志写文件并加锁。

> [!IMPORTANT]
> **Hook 要窄、快、可解释**：越靠近高频事件，越要克制。

## 30. 推荐落地顺序

不要一上来做复杂 Hook 系统。按这个顺序：

1. User scope 加 Notification。
2. Project scope 加格式化 Hook。
3. Project scope 加敏感文件保护。
4. 给 Bash / MCP 高风险调用加 PreToolUse 校验。
5. 需要时加 ConfigChange 审计。
6. 复杂项目再加 CwdChanged / FileChanged 环境重载。
7. 只对低风险、明确事件加 PermissionRequest 自动处理。
8. 最后再考虑 prompt / agent / HTTP / async hooks。

> [!TIP]
> **先从低风险、可观察开始**：通知和格式化最适合入门；自动批准和生产动作最晚考虑。

## 31. 自检清单

学完这一章，你应该能做到：

- 我能解释 Hook 和 prompt / Skill 的区别。
- 我知道 Hook 配在 settings 的 `hooks` block 里。
- 我能用 `/hooks` 查看当前配置。
- 我知道 `PreToolUse`、`PostToolUse`、`PermissionRequest`、`Notification`、`Stop` 的用途。
- 我知道 matcher 匹配对象随事件变化。
- 我知道 `if` 使用 permission rule syntax。
- 我知道 exit 0、exit 2 和其他 exit code 的差别。
- 我知道 structured JSON 输出必须走 stdout。
- 我知道 command、prompt、agent、HTTP、async hooks 的取舍。
- 我知道 Hook 不是 permissions 的替代品。
- 我知道自动批准权限请求必须极窄。
- 我知道怎么排查 hook 不触发和 JSON validation failed。

## 32. 术语速查

- `Hook`：Claude Code 生命周期事件上的自动化动作。
- `Command hook`：运行本机 shell 命令的 Hook。
- `Prompt hook`：用 LLM 根据 hook input 做判断的 Hook。
- `Agent hook`：spawn subagent 做验证的 Hook。
- `HTTP hook`：把事件发到 HTTP endpoint 的 Hook。
- `Async hook`：后台执行、不阻塞当前流程的 Hook。
- `matcher`：按事件对象筛选 hook group。
- `if`：按工具名和参数进一步筛选工具类 hook。
- `PreToolUse`：工具执行前事件。
- `PostToolUse`：工具成功执行后事件。
- `PermissionRequest`：权限弹窗出现前事件。
- `PermissionDenied`：auto mode 拒绝工具调用后事件。
- `Notification`：Claude Code 发送通知时事件。
- `Stop`：Claude 完成响应时事件。
- `ConfigChange`：配置变化事件。
- `CwdChanged`：工作目录变化事件。
- `FileChanged`：监听文件变化事件。
- `CLAUDE_ENV_FILE`：Claude Code Bash preamble 环境文件。
- `hookSpecificOutput`：structured JSON 输出里的事件专属控制字段。

## 33. 官方资料

- [Automate workflows with hooks](https://code.claude.com/docs/en/hooks-guide)
- [Hooks reference](https://code.claude.com/docs/en/hooks)
- [Create custom subagents](https://code.claude.com/docs/en/sub-agents)
- [Extend Claude Code](https://code.claude.com/docs/en/features-overview)
- [Extend Claude with skills](https://code.claude.com/docs/en/skills)
- [Configure permissions](https://code.claude.com/docs/en/permissions)

<Cards>
  <Card href="/docs/claude-code/official/02-extensions-automation/commands" title="Commands" description="Hook 是事件自动化；Commands 是会话内控制入口。下一章讲内置命令、bundled skills 和 MCP prompts。" />
  <Card href="/docs/claude-code/official/02-extensions-automation/subagents" title="Subagents（上一篇）" description="Agent hook 和 Subagent hooks 都依赖 subagent 机制。需要回看隔离 worker 的工具、权限和 memory 边界。" />
  <Card href="/docs/claude-code/official/01-core-capabilities/permissions" title="管理权限" description="Hook 能补充复杂条件和反馈，但真正的工具边界仍应写在 permissions 中。" />
</Cards>
