TL;DR: Hooks 是 Claude Code 的确定性控制层。模型负责推理,Hooks 负责在固定事件上记录、拦截、提醒和验证。它不是第二个 AI,而是纯脚本的规则引擎。

为什么需要 Hooks

CLAUDE.md 可以写"不要修改 .env 文件",但提示词约束本质上是非确定性的——模型可能遵守,也可能在上下文压力下忽略。Hooks 解决的是这个问题:用确定性脚本替代非确定性指令。

三个 Hooks 的独特价值:

系统架构

Hook 系统的核心设计是:模型做决策 → 触发生命周期事件 → 确定性脚本响应。模型不知道 Hook 的存在,也不参与 Hook 的执行。

┌──────────────────────────────────────────────────────────────┐
│                      Claude Code 运行时                       │
│                                                              │
│  ┌─────────┐    ┌──────────┐    ┌─────────┐                 │
│  │ 用户输入 │───→│ 模型推理  │───→│ 工具调用 │                 │
│  └─────────┘    └──────────┘    └────┬────┘                 │
│                                      │                       │
│                        ┌─────────────┼──────────────┐        │
│                        │             │              │        │
│                   PreToolUse    PostToolUse    PostToolUse    │
│                   检查/阻断       记录/验证      Failure      │
│                        │             │           错误处理     │
│                        │             │              │        │
│                   exit 0: 放行   ┌────┴────┐        │        │
│                   exit 2: 阻断   │  执行结果 │        │        │
│                        │         └────┬────┘        │        │
│                        ▼              ▼              ▼        │
│                   [工具实际执行]  [Hook 后处理]  [Hook 后处理]  │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │              其他生命周期事件                           │    │
│  │  Notification · Stop · SubagentStart/Stop             │    │
│  │  InstructionsLoaded · PreCompact                      │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

关键设计决策:Hook 的执行结果通过退出码传递给 Claude Code 运行时,而不是注入到模型推理过程中。这意味着 Hook 不会"改变 AI 的想法",而是在物理层面控制工具调用的放行或阻断。

完整事件列表

每个事件对应一个触发时机和一组适用场景。理解触发时机是正确使用 Hook 的前提。

|
事件
|
触发时机
|
能否阻断
|
典型用途
|
| — | — | — | — |
| PreToolUse |
工具执行前
|
能(exit 2)
|
安全门禁、文件保护、命令过滤
|
| PostToolUse |
工具成功执行后
|
不能
|
自动格式化、变更记录、验证提醒
|
| PostToolUseFailure |
工具执行失败后
|
不能
|
错误收集、失败分析、重试建议
|
| Notification |
需要权限或空闲时
|
不能
|
审计日志、通知转发
|
| SubagentStart |
子代理启动时
|
不能
|
注入安全规则、输出格式约束
|
| SubagentStop |
子代理结束时
|
不能
|
收集结论、审计轨迹、结果摘要
|
| Stop |
主会话结束时
|
不能
|
会话总结、验证报告、审计记录
|
| InstructionsLoaded |
CLAUDE.md 和 rules 加载后
|
不能
|
动态上下文注入、环境检测
|
| PreCompact |
上下文压缩前
|
不能
|
保留关键信息的提示注入
|

PreToolUse 是唯一能阻断工具执行的事件。其他事件都是"观察者"——它们能记录和提醒,但不能阻止工具调用。这个设计是有意的:阻断是一种高风险操作,只应该在工具执行前的安全门禁中使用。

Hook 类型

Hook 有两种类型:commandprompt

Command 类型

Shell 脚本。从 stdin 读取工具输入(JSON 格式),通过退出码传递决策,通过 stdout 传递消息。

stdin → JSON 格式的工具输入
stdout → 传递给 Claude 的消息(可选)
stderr → 日志输出(用户不可见)
exit 0 → 放行(或不阻断的通知)
exit 2 → 阻断(仅 PreToolUse 有效)

Command Hook 的确定性是整个系统的基础。同样的 stdin 输入,同样的退出码。没有"可能阻断",只有"阻断或不阻断"。

Prompt 类型

直接向 Claude 注入文本上下文。不执行脚本,不读 stdin,不做决策。纯粹的"额外提示"。

{
  "type": "prompt",
  "prompt": "每次修改文件后,提醒运行相关测试。"
}

Prompt Hook 适合注入静态规则——那些不需要根据工具输入动态判断的指令。它的优势是零执行成本,劣势是无法做条件判断。

类型选择决策

|
条件
|
用 Command
|
用 Prompt
|
| — | — | — |
|
需要读取工具输入
|

|

|
|
需要条件判断
|

|

|
|
需要阻断
|

|

|
|
需要执行外部命令
|

|

|
|
只需要注入静态文本
|

|

|
|
执行成本敏感
|

|
是(零成本)
|

真实配置

以下是一个包含多种 Hook 的 settings.json 配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/block-sensitive-files.sh"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/block-dangerous-commands.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/log-file-changes.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/session-summary.sh"
          }
        ]
      }
    ],
    "SubagentStart": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "你是一个受限执行环境中的子代理。不要修改 .env、证书或生产配置文件。所有发现必须附带文件路径和行号。"
          }
        ]
      }
    ]
  }
}

配置解读:

  • matcher 字段:正则表达式,匹配工具名称。"Edit|Write" 匹配 Edit 或 Write 工具。"Bash" 匹配 Bash 工具。省略 matcher 则匹配所有工具。

  • hooks 数组:同一事件可以有多个 Hook,按顺序执行。PreToolUse 中任意一个 Hook 返回 exit 2 就会阻断。

  • command 路径:建议使用 .claude/hooks/ 目录统一管理,路径相对于项目根目录。

  • 多层配置:Hook 可以在全局 settings.json 和项目级 .claude/settings.json 中同时存在,项目级优先。

三类 Hook 的工程定位

记录型 Hook

定位:观察者。记录工具调用、参数和结果,不影响执行流程。

适合场景

  • • 审计日志:记录每次文件修改和命令执行

  • • 变更追踪:维护会话内的修改文件列表

  • • 行为分析:统计 Claude Code 使用了哪些工具、频率如何

不适合:安全门禁(无法阻断)、上下文注入(不操作模型输入)

提示型 Hook

定位:上下文增强。在不消耗用户消息的前提下注入额外指令。

适合场景

  • • 提醒规则:“修改后端代码后运行 API 测试”

  • • 注入约束:“只读审查,不要修改文件”

  • • 环境信息:“当前分支是 release/v2.3,不要合并到 main”

不适合:条件判断(无法根据输入动态决定)、阻断操作(无法阻止工具执行)

阻断型 Hook

定位:安全门禁。在工具执行前拦截高风险操作。

适合场景

  • • 文件保护:禁止修改 .env、证书、生产配置

  • • 命令过滤:拦截 rm -rfgit push --force、生产数据库写入

  • • 路径约束:限制 Claude Code 只能操作指定目录

不适合:业务逻辑判断(Hook 应该保持简单)、频繁触发的检查(影响开发体验)

类型选择矩阵

我需要做什么?
│
├─ 记录发生了什么 → 记录型(PostToolUse / Stop)
│
├─ 给 Claude 额外指令 → 提示型(prompt Hook)
│
├─ 阻止危险操作 → 阻断型(PreToolUse, exit 2)
│
└─ 既要记录又要阻止 → 两个 Hook:PreToolUse 阻断 + PostToolUse 记录

Hook 执行流程

以一次 Edit 工具调用为例,展示完整的 Hook 执行流程:

用户:"修改 auth.ts 的登录逻辑"

Claude Code 推理 → 决定调用 Edit 工具修改 auth.ts
│
├─ [1] PreToolUse 触发
│   ├─ block-sensitive-files.sh 运行
│   │   ├─ stdin: {"tool_name":"Edit","tool_input":{"file_path":"/src/auth.ts",...}}
│   │   ├─ 检查文件路径是否匹配敏感模式
│   │   ├─ auth.ts 不匹配 → exit 0(放行)
│   │   └─ stdout: 空
│   │
│   └─ 所有 PreToolUse Hook 放行 → 继续执行
│
├─ [2] Edit 工具执行
│   └─ 文件修改成功
│
├─ [3] PostToolUse 触发
│   ├─ log-file-changes.sh 运行
│   │   ├─ stdin: {"tool_name":"Edit","tool_input":{"file_path":"/src/auth.ts"},"result":"success"}
│   │   ├─ 记录变更到审计日志
│   │   └─ exit 0
│   │
│   └─ 所有 PostToolUse Hook 完成
│
└─ [4] Claude Code 向用户展示修改结果

--- 如果 Edit 工具执行失败 ---

├─ [3'] PostToolUseFailure 触发
│   └─ 错误日志记录
│
--- 如果 PreToolUse 阻断 ---

├─ [1'] PreToolUse 返回 exit 2
│   ├─ 工具调用被取消
│   └─ stdout 中的消息展示给 Claude("BLOCK: ...")
└─ Claude 根据阻断消息调整行为

退出码的完整语义:

|
退出码
|
含义
|
适用事件
|
| — | — | — |
|
0
|
放行 / 正常完成
|
所有
|
|
2
|
阻断工具执行
|
仅 PreToolUse
|
|
其他
|
异常,等同于放行
|
所有
|

设计意图:异常时不阻断。Hook 脚本出错(语法错误、运行时异常)不应该让 Claude Code 不可用。这和 fail-open 的安全模型一致——Hook 是附加控制层,不是核心依赖。

Hook 与 Permission System 的关系

Claude Code 有两层控制:Permission System 和 Hooks。

用户请求 → Claude 推理 → 工具调用决策
                             │
                      ┌──────┴──────┐
                      │ Permission  │  第一层:用户显式授权
                      │   System    │  (允许/拒绝特定工具)
                      └──────┬──────┘
                             │ 通过
                      ┌──────┴──────┐
                      │    Hooks    │  第二层:规则引擎
                      │ (PreToolUse)│  (确定性脚本检查)
                      └──────┬──────┘
                             │ 通过
                      ┌──────┴──────┐
                      │  工具执行    │  实际执行
                      └─────────────┘

两者的职责边界:

  • Permission System:管理"这个工具能不能用"。粗粒度的开/关控制。用户驱动。

  • Hooks:管理"这次调用安不安全"。细粒度的条件检查。规则驱动。

Permission System 说"Bash 可以用",Hooks 说"Bash 可以用,但不能执行 rm -rf"。两者互补,不替代。

Matcher 配置详解

Matcher 是 PreToolUse 和 PostToolUse 的路由机制。它决定一个 Hook 只对哪些工具生效。

{
  "matcher": "Edit|Write",
  "hooks": [...]
}

Matcher 的值是正则表达式,匹配工具名称:

|
Matcher
|
匹配的工具
|
| — | — |
| "Edit" |
Edit
|
| "Edit|Write" |
Edit 或 Write
|
| "Bash" |
Bash
|
| ".*" |
所有工具
|
| "mcp__.*" |
所有 MCP 工具
|
|
省略
|
所有工具
|

常见的 Matcher 配置策略

// 策略 1:只监控文件写入工具
{
  "matcher": "Edit|Write",
  "hooks": [{ "type": "command", "command": "bash .claude/hooks/check-file-writes.sh" }]
}

// 策略 2:只监控命令执行工具
{
  "matcher": "Bash",
  "hooks": [{ "type": "command", "command": "bash .claude/hooks/check-commands.sh" }]
}

// 策略 3:监控所有 MCP 工具调用
{
  "matcher": "mcp__.*",
  "hooks": [{ "type": "command", "command": "bash .claude/hooks/audit-mcp-calls.sh" }]
}

// 策略 4:全局审计(所有工具)
{
  "hooks": [{ "type": "command", "command": "bash .claude/hooks/audit-all.sh" }]
}

Hook 的 stdin 数据格式

Command Hook 通过 stdin 接收 JSON 格式的工具调用信息。理解这个格式是编写 Hook 脚本的基础。

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/src/auth.ts",
    "old_string": "const token = req.headers.authorization;",
    "new_string": "const token = req.headers.authorization?.split(' ')[1];"
  }
}

Bash 工具的输入格式:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules && npm install",
    "description": "Clean install dependencies"
  }
}

Hook 脚本中读取 stdin 的标准写法:

#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

注意 // empty 的用法:当字段不存在时返回空字符串而不是 “null”。这在编写通用 Hook 时很重要——不是所有工具都有 file_path,也不是所有工具都有 command

Hook 配置的层级

Hook 可以在三个层级配置,优先级从高到低:

|
层级
|
文件路径
|
作用域
|
典型用途
|
| — | — | — | — |
|
项目级
| .claude/settings.json |
当前项目
|
项目特定的文件保护和命令过滤
|
|
用户级
| ~/.claude/settings.json |
所有项目
|
全局审计日志、个人偏好
|
|
企业级
|
管理员统一配置
|
团队所有成员
|
安全合规、强制策略
|

配置合并规则

  • • 同一事件的所有层级 Hook 都会执行,不覆盖

  • • 执行顺序:企业级 → 用户级 → 项目级

  • • PreToolUse 阻断:任一层级的 Hook 返回 exit 2 都会阻断

这个设计允许团队设置全局安全策略(企业级),同时不阻止个人或项目添加额外的 Hook。

失败案例:未测试的 Hook 阻断了所有工具调用

经过

一个 4 人前端团队决定为项目添加 PreToolUse Hook,阻止对 package.json 的意外修改。开发者在周五下午写了以下 Hook:

#!/bin/bash
# .claude/hooks/block-package-json.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
if [[ "$FILE_PATH" == *"package.json"* ]]; then
  echo "BLOCK: package.json 修改需要团队 review"
  exit 2
fi

看起来没问题。但周一早上,团队成员开始报告 Claude Code 几乎不可用:

  • • 任何涉及文件的操作都被阻断

  • • 即使是读取 package.json 也被拦截

  • • Claude Code 无法完成任何文件编辑任务

根因

Hook 脚本有两个 bug:

修复

分两步修复:

第一步:立即恢复。禁用 Hook(从 settings.json 中移除或注释),让团队恢复工作。

第二步:正确实现

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/block-package-json.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/block-package-json.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 精确匹配根目录的 package.json
if [[ "$FILE_PATH" == "*/package.json" ]]; then
  echo "BLOCK: 根目录 package.json 修改需要团队 review。使用 npm 命令管理依赖。"
  exit 2
fi

exit 0

改动要点:

  • • 添加 "matcher": "Edit|Write" 确保 Hook 只对写入工具生效

  • • 路径匹配改为 */package.json,只匹配根目录的 package.json

  • • 显式 exit 0 放行不匹配的情况

  • • 阻断消息包含具体的替代操作建议

教训

Hook 部署策略

推荐的渐进式部署流程:

第 1 周:记录模式
├─ 所有 Hook 以 exit 0 运行
├─ 只记录日志(stderr 或文件)
├─ 观察触发频率和匹配准确性
└─ 收集误判和遗漏样本

第 2 周:提醒模式
├─ 对匹配的调用输出提示信息(stdout)
├─ 仍然 exit 0(不阻断)
├─ 用户可以看到 Hook 的判断结果
└─ 根据反馈调整匹配规则

第 3 周起:阻断模式
├─ 对确认的高风险操作返回 exit 2
├─ 保留日志和提示信息
├─ 定期审查阻断记录
└─ 持续优化匹配规则

这个策略的核心逻辑是:Hook 的误判成本很高。误阻断会直接破坏开发体验,误放行会留下安全漏洞。先用观察模式积累数据,再用提醒模式验证可读性,最后才用阻断模式上线。

Hook 审计模板

每个上线的 Hook 应该有对应的文档记录。以下是一个审计模板:

## Hook 审计记录

### 基本信息
- 名称: block-sensitive-files.sh
- 事件: PreToolUse
- Matcher: Edit|Write
- 类型: command(阻断)

### 输入
- 格式: JSON(stdin)
- 关键字段: tool_input.file_path

### 输出
- exit 0: 文件路径不在敏感列表中,放行
- exit 2: 文件路径匹配敏感模式,阻断
- stdout: 阻断原因说明

### 匹配规则
- .env / .env.*
- *.pem / *.key
- infra/prod/**
- migrations/**

### 禁用方法
从 .claude/settings.json 的 hooks.PreToolUse 数组中移除此条目

### 上线日期
2025-03-15

### 上次审查
2025-04-01 - 确认无误判

这个模板确保每个 Hook 的行为、触发条件、输出和禁用方法都有据可查。当 Hook 出现问题时,任何人都能快速理解和处理。

交叉参考

  • • 23 PreToolUse 防护[1]:PreToolUse Hook 的完整实现指南,包含文件保护和命令过滤的真实脚本

  • • 24 PostToolUse / Stop 验证[2]:工具执行后的自动验证和会话结束时的结果记录

  • • 25 Subagent Hooks[3]:给子代理注入上下文和收集结果的 Hook 配置

  • • 26 Hook 设计原则[4]:小、确定、可解释、可回滚的四条原则和工程实践

Hook 与 Rules 和 CLAUDE.md 的决策矩阵

三种机制都能约束 Claude Code 的行为,但工程定位完全不同。选错机制会导致约束无效或维护困难。

决策维度              Hook              Rules (.md)        CLAUDE.md
──────────────────────────────────────────────────────────────────────
执行确定性             100% 确定         非确定(提示词)    非确定(提示词)
上下文消耗             零                低                 高
可阻断工具调用         是(PreToolUse)  否                 否
可审计                 是(退出码+日志)  否                 否
条件判断能力           是(脚本逻辑)     否                 否
运行时成本             有(进程开销)     无                 无
适合约束类型           硬性规则           软性指导           项目上下文
修改后生效             立即               下次对话加载        下次对话加载

选择流程

要约束的行为是什么?
│
├─ 绝对不能违反的规则(禁止修改 .env、禁止 rm -rf)
│   → Hook(PreToolUse,exit 2 阻断)
│   理由:提示词约束不可靠,必须用确定性脚本
│
├─ 建议性指导(修改后端代码要运行测试、PR review 关注安全)
│   → Rules(.claude/rules/ 目录下的 .md 文件)
│   理由:不是硬性约束,作为提示词注入更合适
│
├─ 项目级上下文(项目架构说明、技术栈、代码约定)
│   → CLAUDE.md
│   理由:这是上下文信息,不是约束。模型需要这些信息做出正确决策
│
└─ 多层叠加(重要约束需要多层防护)
    例如:禁止修改 workflow 文件
    Layer 1:CLAUDE.md 中声明"Don't modify .github/workflows/"
    Layer 2:Rules 中强调"Workflow files are protected"
    Layer 3:Hook 阻断(PreToolUse,匹配 .github/workflows/ 路径)
    只有 Layer 3 是可靠的,Layer 1-2 是提示词级的软约束

常见错误:把所有约束都写在 CLAUDE.md 里。一个 2000 行的 CLAUDE.md 里有 100 行是"不要做 X"、“不要做 Y”。这些应该分流——硬性约束用 Hook,软性指导用 Rules,CLAUDE.md 只保留上下文信息。

Hook 脚本系统设计分析

理解 Hook 脚本的输入、输出和环境变量设计,是编写可靠 Hook 的基础。

输入系统

stdin(标准输入):
  格式:JSON
  内容:工具调用的完整信息

  PreToolUse 输入示例:
  {
    "tool_name": "Edit",
    "tool_input": {
      "file_path": "/src/auth.ts",
      "old_string": "...",
      "new_string": "..."
    }
  }

  PostToolUse 输入示例(额外包含执行结果):
  {
    "tool_name": "Edit",
    "tool_input": { ... },
    "tool_result": {
      "status": "success",
      "output": "File edited successfully"
    }
  }

  Bash 工具的特殊字段:
  {
    "tool_name": "Bash",
    "tool_input": {
      "command": "npm test",
      "description": "Run tests",
      "timeout": 120000
    }
  }

输出系统

stdout(标准输出):
  - 内容会被传递给 Claude 作为上下文消息
  - PreToolUse 的 stdout 在 exit 2 时作为阻断原因展示
  - PostToolUse 的 stdout 作为补充信息注入对话
  - 保持简短:建议 ≤ 200 字符
  - 不要输出 JSON 到 stdout——那是给模型看的信息,不是日志

stderr(标准错误):
  - 内容写入 Hook 日志,用户不可见
  - 适合记录调试信息、审计日志
  - 不会影响 Claude 的推理过程

退出码:
  0 → 正常完成/放行
  2 → 阻断(仅 PreToolUse 有效)
  其他 → 异常,等同于放行(fail-open)

环境变量

Hook 脚本可用的关键环境变量:

CLAUDE_PROJECT_DIR    项目根目录的绝对路径
  用途:构建相对于项目的文件路径

CLAUDE_SESSION_ID     当前会话的唯一标识
  用途:关联同一会话的多次 Hook 调用

HOME                  用户主目录
  用途:访问全局配置或日志目录

PATH                  系统 PATH
  用途:确保脚本能找到需要的命令

注意:环境变量中不包含 API key 或 secrets。
Hook 脚本不应该尝试读取 Claude Code 的内部状态。

完整的每种事件类型配置示例

Notification Hook

Notification 事件在 Claude Code 需要用户权限确认或会话空闲时触发。

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/forward-notification.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/forward-notification.sh
# 将通知转发到团队的 Slack 频道(用于监控 AI 行为)

INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude Code notification"')

# 发送到 Slack webhook(URL 存储在环境变量中)
if [ -n "$SLACK_WEBHOOK_URL" ]; then
  curl -s -X POST "$SLACK_WEBHOOK_URL" \
    -H 'Content-Type: application/json' \
    -d "{\"text\": \"[Claude Code] $MESSAGE\"}" > /dev/null 2>&1
fi

exit 0  # Notification 永远不阻断

InstructionsLoaded Hook

InstructionsLoaded 在 CLAUDE.md 和 rules 加载完成后触发。适合注入动态上下文。

{
  "hooks": {
    "InstructionsLoaded": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/inject-runtime-context.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/inject-runtime-context.sh
# 注入运行时环境信息,让 Claude 了解当前状态

BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
HAS_STASH=$(git stash list 2>/dev/null | head -1 | wc -l)
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l)

echo "运行时上下文:"
echo "- 当前分支: $BRANCH"
echo "- 未提交的变更: $UNCOMMITTED 个文件"
if [ "$HAS_STASH" -gt 0 ]; then
  echo "- 注意:有 git stash 存在"
fi

if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
  echo "⚠️ 警告:当前在主分支上操作。建议创建 feature 分支。"
fi

exit 0

PreCompact Hook

PreCompact 在上下文压缩前触发。适合注入"必须保留"的关键信息提示。

{
  "hooks": {
    "PreCompact": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "压缩上下文时,请保留以下关键信息:(1) 当前任务的最终目标 (2) 已经完成的步骤和结果 (3) 还需要完成的具体步骤 (4) 任何已经发现但未解决的错误。不要保留中间探索过程的细节。"
          }
        ]
      }
    ]
  }
}

SubagentStart / SubagentStop Hook

{
  "hooks": {
    "SubagentStart": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "你是受限执行环境中的子代理。不要修改 .env、证书或生产配置文件。所有发现必须附带文件路径和行号。如果发现安全问题,立即停止并报告。"
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/collect-subagent-result.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/collect-subagent-result.sh
# 收集子代理执行结果,写入审计日志

INPUT=$(cat)
AGENT_NAME=$(echo "$INPUT" | jq -r '.agent_name // "unknown"')
RESULT=$(echo "$INPUT" | jq -r '.result // "no result"')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

LOG_ENTRY="[$TIMESTAMP] Agent: $AGENT_NAME | Result: $(echo "$RESULT" | head -c 500)"

# 写入审计日志
echo "$LOG_ENTRY" >> "${CLAUDE_PROJECT_DIR}/.claude/agent-audit.log"

exit 0

Hook 执行生命周期

完整的 Hook 执行生命周期,从注册到触发到结果处理:

注册阶段:
  settings.json 加载 → 解析 hooks 配置
  → 验证 command 路径是否存在(不存在则跳过并警告)
  → 验证 prompt 是否非空
  → 注册到对应事件的监听列表

触发阶段:
  Claude Code 运行时事件发生
  → 按事件类型查找匹配的 Hook 组
  → 如果有 matcher,用正则匹配工具名称
  → 按注册顺序执行匹配的 Hook

执行阶段(Command 类型):
  1. fork 子进程
  2. 通过 stdin 传入 JSON 数据
  3. 设置环境变量(CLAUDE_PROJECT_DIR 等)
  4. 等待执行完成(受 timeout 约束)
  5. 收集 stdout、stderr 和退出码
  6. 处理超时(超时视为异常,等同于放行)

结果处理阶段:
  PreToolUse:
    exit 0 → 放行,继续下一个 Hook 或执行工具
    exit 2 → 阻断,取消工具调用,stdout 作为阻断原因
    其他/超时 → 等同于放行(fail-open)
    stdout → 注入为 Claude 的上下文信息

  PostToolUse / PostToolUseFailure:
    退出码不影响工具执行(已经完成)
    stdout → 注入为 Claude 的上下文信息
    stderr → 写入日志

  Stop / Notification / SubagentStop:
    退出码无实际效果(观察者角色)
    stdout → 注入为上下文(如果会话还在继续)

事件触发时序图

会话生命周期中的 Hook 触发时序:

InstructionsLoaded ──────────────────────────────────────────
  │ CLAUDE.md 和 rules 加载完成后触发
  │ 时机:会话开始时,只触发一次
  ▼
用户输入 ─── Claude 推理 ─── 决定调用工具
                                     │
                              PreToolUse ────────────────────
                                │ 检查/阻断
                                ├─ exit 2 → 阻断,回到 Claude 推理
                                └─ exit 0 → 放行
                                     │
                              工具执行(实际调用)
                                     │
                           ┌─────────┴─────────┐
                           │                   │
                      成功执行             执行失败
                           │                   │
                    PostToolUse      PostToolUseFailure
                      记录/验证         错误处理/日志
                           │                   │
                           └─────────┬─────────┘
                                     │
                          回到 Claude 推理(可能继续调用工具)
                                     │
                              [重复上述循环]
                                     │
  ┌──────────────────────────────────┤
  │                                  │
SubagentStart                   Notification
  │ 子代理启动时触发               │ 需要权限确认或空闲时触发
  │ 注入安全规则                   │ 审计日志、通知转发
  ▼                                ▼
  [子代理内部循环]                  [继续等待或用户响应]
  │
SubagentStop
  │ 子代理结束时触发
  │ 收集结果、审计轨迹
  ▼
                                     │
                              用户结束会话
                                     │
                              Stop ─────────────────────────
                                │ 会话结束时触发
                                │ 会话总结、验证报告、审计记录
                                ▼
                              PreCompact ────────────────────
                                │ 上下文压缩前触发
                                │ 注入"必须保留"的关键信息提示
                                ▼
                              [压缩后继续会话]

权衡

Hook 是代码,不是魔法。它会失败、阻塞、误判。越靠近阻断逻辑,越要保持小而确定。一个 200 行的 Hook 脚本比没有 Hook 更危险——因为它给了你虚假的安全感,同时引入了不确定性。

Hook 解决不了所有安全问题。它能阻止 Claude Code 执行 rm -rf /,但阻止不了模型生成一段看起来正确但实际有逻辑漏洞的代码。安全是一个分层系统,Hook 是其中一层,不是全部。

交叉参考

  • • 23 PreToolUse 防护[1]:PreToolUse Hook 的完整实现指南,包含文件保护和命令过滤的真实脚本

  • • 24 PostToolUse / Stop 验证[2]:工具执行后的自动验证和会话结束时的结果记录

  • • 25 Subagent Hooks[3]:给子代理注入上下文和收集结果的 Hook 配置

  • • 26 Hook 设计原则[4]:小、确定、可解释、可回滚的四条原则和工程实践

引用链接

[1] 23 PreToolUse 防护: ./23-pretooluse-guardrails.md[2] 24 PostToolUse / Stop 验证: ./24-posttooluse-stop-verification.md[3] 25 Subagent Hooks: ./25-subagent-hooks.md[4] 26 Hook 设计原则: ./26-hook-design-principles.md

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐