Agent Hook 深度教程:事件、匹配、处理器、阻止机制一次讲透
从生命周期事件、Matcher、Handler 和 stdin/stdout 协议入门 Agent Hooks,对比 Claude Code、Codex 等工具,并用只读目录保护案例讲解配置、脚本与安全边界。
1. Agent Hooks 是什么,以及底层运行机制
Hook 也就是“钩子”。在 AI Agent 中,它是一种生命周期自动化机制:当 Agent 运行到某个特定节点时,由 Claude Code、Codex 这类 Agent 软件主动触发用户预先配置好的处理器,而不是等待大模型自己决定要不要执行。
Hook 的触发由 Agent 的运行环境控制,不依赖大模型自己“记得”执行。 也就是说,Hook 配置在 Agent 运行环境中,而不是依赖大模型。
不过,“Hook 是确定性的”不能理解成“Hook 永远不会失败”。更准确的说法是:
Hook 的触发机制比自然语言指令更确定,但 Hook 自己仍可能因为脚本报错、超时、配置错误而失败;如果 Hook 内部再次调用大模型进行判断,判断结果本身仍然具有模型的不确定性。
Agent 的完整运行过程可以简单理解为:用户输入 → 大模型推理 → Agent 准备执行工具 → Agent 运行环境检查 Hook → 执行或阻止工具 → 将结果交回大模型 → 大模型继续工作。
Hook 与其他 Agent 机制的区别
| 机制 | 可以怎样理解 | 主要解决的问题 | 是否依赖模型主动执行 |
|---|---|---|---|
| Prompt(提示词) | 当前这次告诉 AI 做什么 | 一次性任务要求 | 是 |
CLAUDE.md / AGENTS.md | 项目长期说明书 | 项目背景、规则、编码规范 | 是 |
| Skill(技能) | 一套可复用工作方法 | 固定流程、专业能力 | 是,通常由用户或 Agent 调用 |
| MCP / Tool(工具) | 给 Agent 增加手脚 | 搜索、数据库、浏览器、GitHub 等能力 | 是 |
| Hook(钩子) | 到了某个节点自动执行 | 自动检查、拦截、记录、验收 | 否,由 Agent 软件触发 |
| Permission / Sandbox(权限 / 沙箱) | 真正的安全边界 | 限制 Agent 可以访问和执行什么 | 否 |
所以可以用一个简单判断:
- 希望 AI 知道什么 →
CLAUDE.md/AGENTS.md - 希望 AI 学会怎么做 → Skill
- 希望 AI 拥有某种外部能力 → MCP / Tool
- 希望系统 到了某个节点自动做什么 → Hook
- 希望 Agent 绝对不能越过某个权限边界 → Permission / Sandbox
Hook 很适合充当动态安全检查,但不应该完全替代权限和沙箱。
2. Hooks 的核心架构:事件、匹配条件、处理器和返回结果
Claude Code 和 Codex 的 Hook 配置都可以用四个概念理解。
| 组成 | 官方术语 | 回答的问题 | 例子 |
|---|---|---|---|
| 事件 | Event | 什么时候启动? | 工具执行前 |
| 匹配条件 | Matcher | 哪些操作才处理? | 只处理 Bash |
| 处理器 | Handler | 触发后运行什么? | Python 安全检查脚本 |
| 返回结果 / 决策 | Result / Decision | 检查之后 Agent 怎么办? | 允许、阻止、反馈、修改参数 |
例如,希望阻止危险 Git 命令:PreToolUse 事件 → 匹配 Bash → 运行 check-git.py → 脚本发现 git reset --hard → 返回“拒绝” → Agent 不执行命令。 |
配置案例:
{
"description": "保护 profile 个人资料目录,只允许读取",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 \"/你的项目绝对路径/.agent-hooks/protect-profile.py\"",
"timeout": 5,
"statusMessage": "正在检查 profile 目录保护规则"
}
]
}
]
}
}
生命周期
flowchart LR
A[会话开始<br/>SessionStart]
--> B[用户提交任务<br/>UserPromptSubmit]
--> C[准备调用工具<br/>PreToolUse]
--> D[工具执行]
--> E[工具执行完成<br/>PostToolUse]
E -->|还要继续工作| C
E -->|上下文过长| F[压缩前<br/>PreCompact]
F --> G[压缩后<br/>PostCompact]
G --> C
E -->|启动子 Agent| H[SubagentStart]
H --> I[SubagentStop]
I --> C
E -->|认为任务完成| J[最终结束检查<br/>Stop]
J -->|检查失败,继续修改| C
J -->|检查通过| K[会话结束<br/>SessionEnd]
事件(Event):什么时候执行
事件是 Agent 运行过程中提前开放出来的固定节点,例如:SessionStart 表示会话开始,PreToolUse 表示工具执行之前,PostToolUse 表示工具执行之后,Stop 表示 Agent 认为任务已经完成。
事件不是大模型主动调用的 Tool,而是 Agent 软件自己产生的生命周期信号。
匹配条件(Matcher):哪些操作才需要检查
一个 PreToolUse 事件可能在一轮任务中出现几十次。如果 Hook 只关心 Shell 命令,就没有必要让文件读取、MCP 调用等操作也启动脚本。
可以先设置:PreToolUse → 只匹配 Bash → 再由脚本判断是不是 git push。这样可以减少不必要的进程启动和性能损耗。
Matcher 并不是所有事件都支持。例如 Codex 当前的 Stop 和 UserPromptSubmit 会忽略 Matcher,所以更复杂的过滤条件需要在脚本内部完成。
处理器(Handler):真正执行什么
处理器就是 Hook 被触发后真正运行的部分。Claude Code 当前支持五类处理器:
| 处理器 | 实际做什么 | 常见用途 |
|---|---|---|
| Command(本地命令) | 运行 Shell、Python、Node.js 等程序 | Lint、测试、安全检查 |
| HTTP(网络请求) | 把 Hook 数据发送到 HTTP 接口 | Webhook、远程审计服务 |
| MCP Tool(MCP 工具) | 自动调用已经连接的 MCP 工具 | 数据库、Memory、Jira 等 |
| Prompt(模型判断) | 再调用一次 Claude 做单轮判断 | 语义质量检查 |
| Agent(子 Agent 判断) | 启动一个可读取文件、搜索代码的验证 Agent | 复杂代码或项目级验收 |
Claude Code 的 Agent Handler 目前仍属于实验功能。
Codex 当前原生执行 Command(本地命令) 和 MCP Tool(MCP 工具) 两类处理器。配置解析器虽然已经认识 prompt 和 agent,但目前会跳过它们;HTTP 请求则需要通过本地脚本发送。
这并不意味着 Codex Hook 不能使用 AI。例如可以:Codex Hook → Python 脚本 → OpenAI API / OpenRouter / 本地模型 / 本地 CLI 命令(如 AGY) → 得到 AI 判断 → Python 将结果返回 Codex。只是这部分需要用户自己写程序,Codex 暂时没有 Claude Code 那种原生 Prompt / Agent Handler。
Hook 如何获得数据,又如何控制 Agent
Command Hook 启动脚本时,Agent 会通过标准输入 stdin 发送一份 JSON 数据。
以工具执行前为例,内容大致如下:
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "git reset --hard"
}
}
Python 或 Node.js 脚本读取这份 JSON,就知道:当前发生了什么事件、Agent 准备调用哪个工具、Tool 的具体参数是什么。
脚本处理之后,再通过标准输出 stdout 返回 JSON。
例如 Claude Code 和 Codex 当前的 PreToolUse 都支持下面这种拒绝格式:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "检测到危险 Git 命令,已阻止执行。"
}
}
这里也是 Hooks 最容易产生误解的地方:不同事件中的“阻止”含义并不相同。
| 事件 | Hook 返回阻止后实际发生什么 |
|---|---|
UserPromptSubmit | 用户 Prompt 不继续交给模型处理 |
PreToolUse | Tool 尚未执行,可以真正阻止副作用 |
PostToolUse | Tool 已经执行,无法撤销,只能反馈问题并让 Agent 修正 |
SubagentStop | 子 Agent 不结束,继续处理问题 |
Stop | 主 Agent 不结束,根据 Hook 给出的原因继续工作 |
所以,安全检查应该尽量发生在 PreToolUse,而不是等到 PostToolUse 才发现危险操作。
3. Claude Code、Codex 与其他 Agent 的 Hooks 对比
截至 2026 年 8 月,Claude Code 和 Codex 都已经拥有正式的生命周期 Hook 系统,但 Claude Code 暴露的事件和处理器明显更多。
Claude Code 与 Codex
| 能力 | Claude Code | Codex |
|---|---|---|
| 生命周期事件数量 | ✅ 31 类,覆盖范围非常细 | ✅ 11 类,覆盖核心生命周期 |
| 核心生命周期事件 | ✅ SessionStart、UserPromptSubmit、PreToolUse、PermissionRequest、PostToolUse、Pre/PostCompact、SubagentStart/Stop、Stop、SessionEnd 均支持 | ✅ 上述核心事件基本全部支持 |
| 扩展生命周期事件 | ✅ 额外支持文件变化、配置变化、CLAUDE.md 加载、Tool 失败、并行 Tool 完成、Task、Agent Team、Worktree、Notification、MCP 用户交互等 | ❌ 暂无这些细粒度事件 |
| 本地命令 Handler | ✅ 支持 Shell、Python、Node.js 等 | ✅ 支持 Shell、Python、Node.js 等 |
| HTTP Handler | ✅ 原生支持 | ❌ 不支持,需要通过本地脚本发送 HTTP 请求 |
| MCP Tool Handler | ✅ 原生支持 | ✅ 原生支持 |
| Prompt 模型判断 Handler | ✅ 原生支持,可直接调用模型进行语义判断 | ❌ 当前能解析配置,但不会执行 |
| Agent 验证 Handler | 🧪 原生支持,可启动验证 Subagent,目前属于实验功能 | ❌ 当前能解析配置,但不会执行 |
| 工具执行前拦截 | ✅ PreToolUse 可允许、拒绝、要求授权,并可修改 Tool 输入 | ✅ PreToolUse 可允许、拒绝,并可修改部分 Tool 输入;暂不支持 ask |
| 工具执行后的检查与反馈 | ✅ PostToolUse 可反馈问题,还支持更完整的 Tool Output 改写 | ✅ PostToolUse 可反馈问题,但结果改写能力相对有限 |
| Tool 覆盖范围 | ✅ Bash、文件读写、MCP、Agent、WebSearch、WebFetch 等大量内置 Tool 都可进入 Hook | ⚠️ Bash、apply_patch、MCP 和多数本地 Tool 可进入 Hook;WebSearch 等 Hosted Tool 不经过 Pre/PostToolUse |
| Matcher 精细过滤 | ✅ Matcher 之外还有 if,可进一步按具体命令过滤,如只匹配 git push | ⚠️ 支持 Matcher;更细的参数判断通常需要放进 Python / Node.js 脚本 |
| 异步 Hook | ✅ 支持,适合后台测试、通知、日志 | ✅ 支持,适合后台测试、通知、日志 |
| Hook 配置作用域 | ✅ 用户级、项目级、本机项目级、Plugin、Skill、Subagent、Managed Policy | ✅ 用户级、项目级、Plugin、Managed 配置;作用域相对少 |
| 第三方 Hook 安全审核 | ✅ 主要依靠 Workspace Trust 等机制 | ✅ Workspace Trust + 独立 Hook Review;Hook 修改后会重新审核 |
| 整体成熟度 | 🟢 功能更完整,已经接近完整的 Agent 生命周期扩展框架 | 🟡 核心功能已经完善,覆盖绝大多数个人用户场景,但高级扩展能力仍少于 Claude Code |
对于普通用户,差距没有表格看起来那么大。 如果需求只是运行 Python / Node.js 脚本、Lint、测试、检查 Git、保存记忆、发送 HTTP 请求,Claude Code 和 Codex 基本都可以完成,因为 Command Hook 本身已经可以执行任意本地程序。
Claude Code 真正明显领先的地方,是可以不写额外程序,直接让 Prompt Handler 或 Agent Handler 做 AI 语义判断。
举例: 【Prompt 模型判断 Handler】实际上是【让 Hook 临时用 AI 做一次判断】。 【Agent 验证 Handler】实际上是【派一个子 Agent 去调查后再判断】。 Codex 目前都没有。
其他主流 Agent
这些 Agent 虽然都存在类似 Hook 的生命周期扩展机制,但实现方式并没有统一。
| Agent | Hook 实现方式 | 工具执行前拦截 | 普通用户实现难度 | 核心特点 |
|---|---|---|---|---|
| Claude Code | JSON 配置 + Command / HTTP / MCP / Prompt / Agent | 支持 | 低~中 | 当前最完整的原生 Hook 系统之一 |
| Codex | hooks.json / config.toml + Command / MCP Tool | 支持 | 低 | 核心生命周期完整,处理器目前偏简单 |
| OpenCode | JavaScript / TypeScript Plugin 事件 | 支持 | 中 | tool.execute.before/after 等事件直接写进插件 |
| Hermes Agent | Shell Hook、Plugin Hook、Gateway Hook、Outbound Webhook | 支持 | 低~高 | 普通用户可直接用 Shell Hook,复杂需求再写插件 |
| OpenClaw | Internal Hook + Typed Plugin Hook | 支持 | 中~高 | 普通自动化与真正的运行时拦截分成两套机制 |
| DeepSeek Harness | Cordis Plugin + 类型化事件系统 | 取决于具体事件 | 高 | 整个 Harness 都采用插件/事件架构,目前仍偏开发者平台 |
OpenCode 没有要求用户建立 Claude 风格的 hooks.json。项目目录中的 .opencode/plugins/*.js 或 .ts 文件会被自动加载,插件可以监听 tool.execute.before、tool.execute.after、session.compacted 等事件。
Hermes Agent 目前有四套 Hook。对于个人用户,最值得关注的是 Shell Hook:直接在 ~/.hermes/config.yaml 中声明事件并指向脚本,可以实现工具拦截、格式化、上下文注入等功能;只有更复杂的需求才需要写 Python Plugin。
OpenClaw 把两类需求分得更明确:Internal Hook 更适合 /new、/reset、Gateway 启动、消息等自动化;如果需要在工具执行前拦截、修改 Prompt 或控制 Agent 流程,则需要 Typed Plugin Hook。
DeepSeek Harness 比较特殊,它基于 Cordis,也就是【万物接插件】。 官方 Hook 桥接插件:
| 官方插件 | 用途 |
|---|---|
@deepseek-ai/dsh-hooks-claude-code | 读取 Claude Code Hook 配置 |
@deepseek-ai/dsh-hooks-codex | 读取 Codex hooks.json |
假设你已经有 Codex Hook:
my-project/
├── .codex/
│ └── hooks.json
└── .agent-hooks/
└── protect-profile.py
在 ~/.dsh/cordis.patch.yml 中配置:
- insert:
- id: hooks-codex
name: '@deepseek-ai/dsh-hooks-codex'
config:
configPath: '/Users/username/my-project/.codex/hooks.json'
普通用户真正需要掌握的核心事件
没有必要记住 Claude Code 的 31 个事件。下面几个已经覆盖绝大多数个人使用场景:
| 事件 | 中文理解 | 典型用途 | Claude Code | Codex |
|---|---|---|---|---|
SessionStart | Agent 开始工作 | 加载动态项目状态、长期记忆 | 支持 | 支持 |
UserPromptSubmit | Prompt 交给模型前 | 检索记忆、加入上下文、检查输入 | 支持 | 支持 |
PreToolUse | Agent 真正动手前 | 危险命令、文件、MCP 操作拦截 | 支持 | 支持 |
PostToolUse | Agent 动手之后 | Lint、格式化、自动检查 | 支持 | 支持 |
PreCompact | 上下文压缩前 | 保存决策、进度、长期记忆 | 支持 | 支持 |
SubagentStop | 子 Agent 准备结束 | 检查研究、代码等子任务质量 | 支持 | 支持 |
Stop | Agent 认为任务完成 | 最终测试、Review、完成条件检查 | 支持 | 支持 |
另外值得认识 PermissionRequest:它发生在 Agent 已经准备向用户请求权限的时候,可以自动批准、拒绝或介入权限流程。它和 PreToolUse 不完全是一回事。
4. 普通用户应该编写哪些 Hooks:实际应用场景
普通用户通常不需要几十个 Hook。真正高价值的场景主要集中在安全、自动检查、最终验收、长期记忆和通知。
| 使用场景 | 推荐事件 | 具体实现 | 价值 |
|---|---|---|---|
| 阻止危险 Shell / Git 命令 | PreToolUse | Python / Node.js 检查命令 | 极高 |
| 防止修改敏感文件 | PreToolUse | 检查文件路径,如 .env、密钥文件 | 高 |
| 控制危险 MCP 写操作 | PreToolUse | 匹配特定 MCP Tool 并检查参数 | 高 |
| 修改代码后运行 Lint | PostToolUse | ESLint、Ruff、Prettier 等 | 高 |
| 修改代码后运行局部测试 | PostToolUse | 只测试受影响模块 | 高 |
| 任务结束前运行测试 | Stop | npm test、pytest 等 | 极高 |
| 完成前检查任务要求 | Stop | 普通脚本或 AI 判断 | 中~高 |
| 会话开始恢复记忆 | SessionStart | 从本地 Memory 文件 / 数据库读取 | 进阶 |
| Prompt 前检索相关记忆 | UserPromptSubmit | Memory Search / RAG | 进阶 |
| 上下文压缩前保存状态 | PreCompact | 保存决策、进度、未完成事项 | 进阶 |
| 检查子 Agent 产出 | SubagentStop | 检查来源、测试、交付物 | 进阶 |
| Agent 完成后通知 | Stop / Notification 类事件 | macOS 通知、Slack、Telegram | 按需 |
最值得普通用户先建立的三个 Hook
1. PreToolUse:危险操作保护
例如拦截:git reset --hard、git push --force、大范围删除命令、写入生产数据库、删除重要目录、高风险 MCP 写操作。这是最直接的安全收益。
2. PostToolUse:修改后的轻量自动检查
例如:修改 TypeScript → ESLint / TypeScript Check、修改 Python → Ruff / Pyright、修改 Markdown → Markdownlint、修改 UI → Design Detector。
这里应该做轻量、快速、局部检查,而不是每次修改一个文件就运行整个项目的完整测试。
3. Stop:最终完成条件检查
例如要求:测试必须通过、Lint 必须通过、指定文件必须已经生成、研究任务必须包含来源、用户要求的几个子任务不能遗漏。
Stop 最大的价值是解决 Agent 很常见的问题:
模型自己认为已经做完了,但实际上仍然有遗漏。
5. 从 0 创建并编写一个 Hook
flowchart TD
A[明确需求<br/>想自动检查什么] --> B[选择触发事件<br/>例如 PreToolUse / PostToolUse / Stop]
B --> C[在 .codex/hooks.json<br/>配置 Hook]
subgraph SCRIPT[编写 Hook 脚本(让AI写!)]
direction TD
D[编写 Python / Node.js 脚本]
E[读取 Codex 传入的 JSON]
F[执行检查并返回<br/>允许 / 阻止 / 反馈]
D --> E --> F
end
C --> D
F --> G[用 /hooks 检查配置<br/>实际测试 Hook]
下面用一个更贴近日常使用的真实案例完整走一遍:
项目中的
profile/文件夹存放个人资料。Agent 可以读取里面的内容,但禁止修改、删除已有文件,也禁止在里面新建文件。
也就是说:
| Agent 操作 | 是否允许 |
|---|---|
读取 profile/about.md | ✅ 允许 |
搜索 profile/ 中的内容 | ✅ 允许 |
| 修改已有文件 | ❌ 阻止 |
| 删除已有文件 | ❌ 阻止 |
| 新建文件 | ❌ 阻止 |
这个案例可以同时展示 PreToolUse(工具执行前事件)、Matcher(匹配条件)、Command Handler(本地脚本处理器)、JSON 输入输出,以及 Claude Code 和 Codex 如何共用一个 Python 检查脚本。
项目中准备三个文件:
.agent-hooks/protect-profile.py:真正执行检查的公共 Python 程序.claude/settings.json:Claude Code 的 Hook 配置.codex/hooks.json:Codex 的 Hook 配置
第一步:把自然语言规则拆成 Hook
| 问题 | 本案例答案 |
|---|---|
| 什么时候检查? | Agent 真正修改文件之前 |
| 使用哪个事件? | PreToolUse(工具执行前) |
| 检查哪些操作? | 文件修改工具 + Shell 命令 |
| 哪个目录受到保护? | 项目根目录下的 profile/ |
| 谁负责判断? | Python 脚本 |
| 发现修改行为怎么办? | 返回 deny(拒绝),Tool 不执行 |
读取 profile/ 怎么办? | 不拦截,正常允许 |
最关键的是选择 PreToolUse。如果改用 PostToolUse,Hook 被触发的时候文件已经被修改了,只能发现问题,不能阻止修改发生。
第二步:编写保护脚本
创建:
.agent-hooks/protect-profile.py
#!/usr/bin/env python3
import json
import re
import sys
from pathlib import Path
# 当前脚本位于:
# <项目>/.agent-hooks/protect-profile.py
#
# 因此脚本的上一级目录就是项目根目录,
# profile/ 位于项目根目录下面。
PROJECT_ROOT = Path(__file__).resolve().parent.parent
PROTECTED_DIR = (PROJECT_ROOT / "profile").resolve()
def deny(reason: str) -> None:
"""告诉 Claude Code / Codex:拒绝这次 Tool 调用。"""
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason
}
}, ensure_ascii=False))
sys.exit(0)
def is_in_profile(path_text: str) -> bool:
"""判断一个文件路径是否位于 profile/ 目录中。"""
path = Path(path_text).expanduser()
if not path.is_absolute():
path = PROJECT_ROOT / path
try:
path.resolve().relative_to(PROTECTED_DIR)
return True
except ValueError:
return False
# Agent Runtime 会通过标准输入 stdin
# 把当前 Tool 调用信息作为 JSON 传给脚本。
event = json.load(sys.stdin)
tool_name = event.get("tool_name", "")
tool_input = event.get("tool_input") or {}
# --------------------------------------------------
# 1. Claude Code:Write / Edit
# --------------------------------------------------
#
# Claude Code 修改文件时,会直接提供 file_path。
# 如果目标位于 profile/ 中,直接拒绝。
if tool_name in {"Write", "Edit"}:
file_path = tool_input.get("file_path")
if isinstance(file_path, str) and is_in_profile(file_path):
deny(
"profile/ 是只读目录:"
"允许读取,但禁止新建、修改或覆盖其中的文件。"
)
# --------------------------------------------------
# 2. Codex:apply_patch
# --------------------------------------------------
#
# Codex 的文件新增、修改、删除通常通过 apply_patch 完成。
# Hook 收到的 command 中会包含这次 Patch 的内容。
#
# 教学案例中,只要 Patch 明确涉及 profile/,
# 就阻止整个文件修改操作。
if tool_name == "apply_patch":
patch = tool_input.get("command", "")
if isinstance(patch, str):
normalized = patch.replace("\\", "/")
if re.search(r"(^|[\s/])profile/", normalized, re.MULTILINE):
deny(
"检测到 Codex 准备修改 profile/:"
"这个目录只允许读取,禁止新增、修改或删除文件。"
)
# --------------------------------------------------
# 3. Shell 命令
# --------------------------------------------------
#
# Agent 也可能不用专门的文件编辑工具,
# 而是通过 rm、mv、touch、重定向等命令修改文件。
#
# 这里拦截常见的写入方式。
if tool_name in {"Bash", "PowerShell"}:
command = tool_input.get("command", "")
if isinstance(command, str):
normalized = command.replace("\\", "/")
touches_profile = bool(
re.search(r"(^|[\s\"'])\.?/?profile(?:/|[\s\"']|$)", normalized)
)
write_patterns = [
r"\brm\b",
r"\brmdir\b",
r"\bmv\b",
r"\btouch\b",
r"\bmkdir\b",
r"\btee\b",
r"\bsed\s+-i\b",
r"\bchmod\b",
r"\bchown\b",
r">>",
r"(?<!>)>(?!>)"
]
attempts_write = any(
re.search(pattern, normalized)
for pattern in write_patterns
)
if touches_profile and attempts_write:
deny(
"检测到 Shell 命令准备修改 profile/:"
"这个目录只允许读取,禁止新增、修改或删除文件。"
)
# 没有发现违规行为:
# 不输出拒绝结果,正常退出,Agent 继续执行原操作。
sys.exit(0)
这个脚本实际做的事情并不复杂:Agent 准备操作 → Hook 把操作信息交给 Python → Python 判断是否要修改 profile/ → 如果是,就返回拒绝 → Agent 的 Tool 不执行。
Claude Code 和 Codex 的文件编辑方式有所不同,因此脚本分别进行了处理:
| Agent | 常见文件修改方式 | Hook 实际检查什么 |
|---|---|---|
| Claude Code | Write / Edit | tool_input.file_path |
| Codex | apply_patch | tool_input.command 中的 Patch |
| 两者 | Shell 命令 | tool_input.command |
读取操作不在这里阻止,因此 Agent 仍然可以读取 profile/ 中的信息。
第三步:连接 Claude Code
在项目的:
.claude/settings.json
加入:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR}/.agent-hooks/protect-profile.py\"",
"timeout": 5
}
]
}
]
}
}
几个关键字段:
| 配置 | 含义 |
|---|---|
PreToolUse | Agent 真正执行 Tool 之前进行检查 |
| `Write | Edit` |
| `Bash | PowerShell` |
type: "command" | Hook 运行一个本地程序 |
command | 执行刚才创建的 Python 脚本 |
timeout: 5 | 最多等待脚本 5 秒 |
这里没有匹配 Read。
因此:
Agent 读取 profile/about.md → 不触发这个保护 Hook → 正常读取。
Agent 修改 profile/about.md → Edit → 触发 Hook → Python 判断路径 → 返回 deny → 修改被阻止。
Claude Code 可以通过 /hooks 查看当前已经加载的 Hook。
第四步:连接 Codex
Codex 的项目级配置写在:
.codex/hooks.json
{
"description": "保护 profile 个人资料目录,只允许读取",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 \"/你的项目绝对路径/.agent-hooks/protect-profile.py\"",
"timeout": 5,
"statusMessage": "正在检查 profile 目录保护规则"
}
]
}
]
}
}
这里需要把:/你的项目绝对路径/ 替换成当前项目的真实路径。例如项目位于:/Users/username/my-project,那么:"command": "python3 \"/Users/username/my-project/.agent-hooks/protect-profile.py\""
Codex 修改文件时,底层使用的 Tool 名通常是 apply_patch,但官方允许 Matcher 使用 Edit 或 Write 作为它的别名,所以这里的:"matcher": "Bash|Edit|Write" 既可以检查 Shell 命令,也可以检查 Codex 的文件新增、修改和删除操作。
Codex 可以通过 /hooks 查看项目 Hook,并完成首次安全审核。
第五步:测试 Hook
测试读取:应该成功
向 Claude Code 或 Codex 输入:
读取
profile/about.md,告诉我里面写了什么,不要修改任何内容。
预期结果:允许。
测试修改:应该被阻止
输入:
把
profile/about.md第一段改成“测试内容”。
预期结果:Hook 在真正修改之前触发,并拒绝操作。
Agent 应该收到类似:
profile/ 是只读目录,允许读取,但禁止新建、修改或覆盖其中的文件。
测试新建文件:应该被阻止
输入:
在
profile/下新建一个test.md,内容写“Hello”。
预期结果:拒绝。
测试删除:应该被阻止
输入:
删除
profile/test.md。
预期结果:拒绝。
这个案例真正展示了 Hook 的什么能力
如果只在 CLAUDE.md 或 AGENTS.md 中写:profile/ 只能读取,绝对不能修改。这仍然是一条给大模型看的自然语言规则。模型通常会遵守,但它仍然需要自己理解、记住并执行这条规则。
Hook 则把规则变成:Agent 每次准备修改文件 → Agent 软件自动运行检查程序 → 发现目标位于 profile/ → 在真正修改之前强制拒绝。
因此这里最重要的不是 Python 代码,而是控制层发生了变化:自然语言规则负责告诉 AI 应该怎么做;Hook 负责在关键节点真正检查 AI 准备做什么。
安全提醒:这个示例主要用于理解 Hook,不能把它当作真正的隐私安全沙箱。
原因是 Shell 命令可以非常复杂,例如 Agent 可以运行一个已有 Python 程序,而这个程序内部再去修改
profile/;单纯检查 Shell 命令文本不一定能够发现这种间接写入。 如果profile/中真的存放重要隐私数据,更可靠的方案应该是: 文件系统只读权限 / Agent Sandbox + Hook 动态检查 Hook 负责提供更友好的、针对 Agent 行为的动态保护;真正不可突破的权限边界应该交给操作系统权限或 Agent 沙箱。
6. 多个 Agent 如何共用一套 Hooks
Claude Code、Codex、Hermes、OpenCode 的 Hook 配置格式并不统一,因此通常不能把一份 .claude/settings.json 直接复制给所有 Agent。
但真正有价值的部分不是配置 JSON,而是业务逻辑(也就是 Hook 脚本)。前面的案例已经展示了最简单的跨 Agent 共用方式:
Claude Code .claude/settings.json → .agent-hooks/check-dangerous-git.py
Codex .codex/hooks.json → .agent-hooks/check-dangerous-git.py
两边配置不同,但实际安全检查只有一份。
哪些部分可以共用
| 层次 | 是否适合共用 | 示例 |
|---|---|---|
| Agent 自己的 Hook 配置 | 通常不能直接共用 | .claude/settings.json、.codex/hooks.json |
| 事件名称 | 部分可以 | 两边都有 PreToolUse、Stop |
| Agent 输入 JSON | 很相似,但不能假设完全相同 | tool_name、tool_input |
| 真正检查规则 | 最适合共用 | Git Policy、Lint、测试、Memory |
| 返回 JSON | 部分相同,部分需要转换 | permissionDecision、decision |
因此一个同时支持多个 Agent 的项目,可以把公共程序单独放到:
.agent-hooks/check-dangerous-git.py.agent-hooks/check-sensitive-files.py.agent-hooks/run-quality-check.py.agent-hooks/save-memory.py
Claude Code、Codex、Hermes 等各自只负责在正确时间调用这些程序。
7. 普通用户的使用建议与注意事项
Hooks 的核心目标是把真正不能依赖模型自觉执行的少数动作移到自动化层。
| 问题 | 建议 |
|---|---|
| Hook 应该装多少? | 从 2~3 个真正高价值 Hook 开始,不要追求数量 |
| 什么最值得先做? | PreToolUse 安全检查、PostToolUse 轻量检查、Stop 最终验收 |
| 所有规则都做成 Hook? | 不要。普通项目规则继续放 CLAUDE.md / AGENTS.md |
| 能用代码判断还要不要 AI? | 不要。正则、Lint、测试能解决的问题优先使用确定性程序 |
PostToolUse 能保护危险操作吗? | 不能作为事前保护,因为 Tool 已经执行 |
| Hook 能代替 Sandbox 吗? | 不能,Hook 更适合作为动态 Guardrail(防护规则) |
| 高频 Hook 能跑完整测试吗? | 不建议,会显著拖慢 Agent |
| Stop Hook 最大风险是什么? | Agent 不断“检查失败 → 修改 → Stop → 再失败”的循环 |
| 可以直接安装陌生项目的 Hook 吗? | 不应该,Command Hook 本质上拥有本地代码执行能力 |
| 多个 Hook 有固定执行顺序吗? | 不要默认有,不同 Agent 的并发和顺序语义不同 |
| 多 Agent 一开始就需要统一框架吗? | 不需要,优先共享 Python / TypeScript 核心脚本 |
| Hook 能看到 Agent 所有操作吗? | 不能保证,具体取决于 Agent 暴露哪些生命周期事件 |