AI 编程教程中文版
官方教程中文版扩展与自动化

使用 Hooks

Claude Code Hooks 在生命周期事件上执行确定性自动化。学会事件、matcher、if、exit code、JSON 输出、command/prompt/agent/HTTP hooks 和安全边界。

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

这一章用 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 是事件发生时直接运行。

第一性原理:需要推理和自由裁量,用 Skill 或 Subagent;需要每次固定执行,用 Hook;需要安全硬边界,优先 permissions + Hook。

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 给反馈。

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 生命周期内生效

基础结构:

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

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

查看入口:

/hooks

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

Project Hook 适合团队规则;User Hook 适合个人通知和日志;Local Hook 适合本机路径;Managed Hook 适合组织强制自动化。

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

macOS 示例:

{
  "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 跑一次:

osascript -e 'display notification "test"'

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

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 前后。
  • InstructionsLoadedCLAUDE.md 或 rules 加载到上下文。
  • Elicitation / ElicitationResult:MCP elicitation 相关事件。

事件决定能力边界:阻断工具要用 PreToolUse;工具完成后格式化用 PostToolUse;权限弹窗自动处理用 PermissionRequest;上下文补充用 SessionStartPostCompact

6. Hook 怎么收到输入

Command hook 会从 stdin 收到 JSON。

常见字段包括:

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

例如 PreToolUse / PostToolUse 里,通常会有:

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

脚本里一般用 jq 解析:

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

先保存输入样本:写复杂 Hook 前,先把 stdin JSON append 到临时日志,确认字段名和事件结构。

7. Exit code 语义

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

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

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

#!/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

不要混用 exit 2 和 JSON allow/deny:官方说明 exit 2 时 JSON 会被忽略。要结构化控制,用 exit 0 + stdout JSON。

8. Structured JSON 输出

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

PreToolUse 可以返回:

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

效果:

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

PermissionRequest 可以返回 allow:

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

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

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

自动 allow 要极窄PermissionRequest matcher 不要写空或 .*,否则可能自动批准文件写入、shell 命令和外部工具。

9. matcher 怎么工作

matcher 用来筛选某类事件。

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

  • PreToolUse / PostToolUse:工具名,例如 BashEditWritemcp__github__.*
  • Notification:通知类型,例如 permission_promptidle_prompt
  • SubagentStart / SubagentStop:agent type,例如 ExplorePlan、custom agent name。
  • PreCompact / PostCompactmanualauto
  • ConfigChangeuser_settingsproject_settingslocal_settingspolicy_settingsskills
  • FileChanged:要监听的 literal filenames,例如 .envrc|.env
  • SessionEnd:结束原因,例如 clear

一些事件不支持 matcher,会每次触发:

  • UserPromptSubmit
  • PostToolBatch
  • Stop
  • TaskCreated
  • TaskCompleted
  • CwdChanged
  • WorktreeCreate
  • WorktreeRemove

先用 matcher 粗筛:例如 Edit|Write。再用 if 或脚本内部逻辑做细筛。

10. if 字段:按工具名和参数过滤

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

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

例子:只检查 git 命令,而不是所有 Bash。

{
  "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 运行。

matcher 按事件对象过滤,if 按工具和参数过滤。需要按 Bash 子命令、Edit 路径、MCP tool 细分时,用 if

11. PostToolUse:编辑后自动格式化

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

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

这个 Hook 的执行逻辑:

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

边界:

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

格式化适合 PostToolUse:不要让 Claude 自己记住“改完跑 formatter”。让 Hook 负责。

12. PreToolUse:阻止受保护文件

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

#!/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

注册:

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

脚本要可执行:

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

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

13. SessionStart / PostCompact:注入上下文

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

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

{
  "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、环境状态。
  • 不要注入大段内容,避免上下文污染。

静态规则进 CLAUDE.md,动态上下文用 Hook

14. ConfigChange:审计配置变化

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

示例:记录配置变化。

{
  "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。

审计日志不要写敏感值:只记录文件、来源、时间和动作,避免把 token、路径隐私和账号写进日志。

15. CwdChanged / FileChanged:重载环境

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

SessionStart + CwdChanged 示例:

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

监听特定文件:

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

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

加载 .env 要小心:不要把敏感值打印到 stdout。环境注入写 CLAUDE_ENV_FILE,不要回显到对话。

16. PermissionRequest:自动批准要极窄

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

示例:只自动批准 ExitPlanMode

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

不要这样做:

  • 空 matcher 自动批准所有请求。
  • .* 自动批准所有请求。
  • BashEdit、MCP 写操作做无条件 allow。

PermissionRequest Hook 是高风险能力:只自动处理极小、明确、低风险的权限项。其他保持 ask。

17. PermissionDenied:让 auto mode 重试

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

某些情况下,你可以返回:

{
  "retry": true
}

告诉模型这次可以重试。

适合:

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

不适合:

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

retry 不是 allow all:它只是让模型可重试被 auto mode 拒绝的调用。仍要窄匹配、可解释。

18. Prompt-based hooks

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

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

{
  "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 做轻量评估。

不适合:

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

Prompt hook 是判断,不是执行:需要实际查代码状态时用 agent hook 或 command hook。

19. Agent-based hooks

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

适合:

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

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

{
  "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。
  • 更贵、更慢、更复杂。

Agent hook 不适合替代 CI:它可以做本地预检,但真正发布前仍要依赖可重复的测试和 CI。

20. HTTP hooks

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

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

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

安全要点:

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

HTTP hook 是数据外发通道:默认只发送必要字段。不要把 transcript 或 tool input 原样推到第三方。

21. Async hooks

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

适合:

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

不适合:

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

阻断用同步,旁路用 async:需要影响当前流程就不要异步。

22. Subagent hooks

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

示例:

---
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 不支持 hooksmcpServerspermissionMode 这几个字段。

Project settings 也有:

  • SubagentStart
  • SubagentStop

适合做 setup / cleanup。

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 或强制策略。

Hook 不是权限系统替代品:权限边界写 permissions;Hook 用来补自动化、反馈、审计和复杂条件判断。

24. Hook 合并和执行模型

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

这带来几个后果:

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

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。

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。

先写最小 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。

stdout 是协议通道:不要随便 echo debug 到 stdout。调试日志写 stderr 或文件。

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

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

常见原因:

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

修法:

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

Stop hook 要能收敛:它应该推动完成,不应该把会话锁进无法满足的循环。

29. 常见故障:Hook 影响太大

表现:

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

处理:

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

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。

先从低风险、可观察开始:通知和格式化最适合入门;自动批准和生产动作最晚考虑。

31. 自检清单

学完这一章,你应该能做到:

  • 我能解释 Hook 和 prompt / Skill 的区别。
  • 我知道 Hook 配在 settings 的 hooks block 里。
  • 我能用 /hooks 查看当前配置。
  • 我知道 PreToolUsePostToolUsePermissionRequestNotificationStop 的用途。
  • 我知道 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. 官方资料

本页目录