> ## Documentation Index
> Fetch the complete documentation index at: https://aicoding.cscitech.top/llms.txt
> Use this file to discover all available pages before exploring further.

# 04-Rules 与 Hooks

> 区分 Codex Rules 的命令准入控制与 Hooks 的生命周期脚本，掌握匹配、配置、stdin/stdout、退出码、阻断、调试和安全边界。

## 本页解决什么问题

Rules 和 Hooks 都能把重复要求变成可执行配置，但职责不同：

* \*\*Rules（规则）\*\*判断命令是否允许执行，以及是否需要审批。
* \*\*Hooks（钩子）\*\*在会话或工具生命周期的固定节点运行脚本。

先记住：**Rules 是命令闸门，Hooks 是生命周期扳机。** Rules 不是自然语言提示，Hooks 也不是沙箱的替代品。具体字段和事件可能随 Codex 版本变化，操作前请核对本机 `codex --help`、相关子命令帮助和官方文档。

本页包含：

* `.rules`、`prefix_rule`、命令前缀和 `allow` / `prompt` / `forbidden`；
* 复合命令评估与 `codex execpolicy check`；
* Hooks 的事件、`matcher`、配置层级和信任；
* `stdin` JSON、`stdout` JSON、stderr 和退出码；
* 事前阻断、事后反馈、`Stop` 续轮的区别；
* 格式化、日志、危险命令检查和会话上下文示例；
* 日志调试、失败策略、回滚和安全边界。

## 开始前检查

第一次实验建议使用临时 Git 仓库，不要在生产目录、主目录或包含客户资料的目录中测试：

```bash theme={null}
mkdir codex-rules-hooks-demo
cd codex-rules-hooks-demo
git init
codex --version
codex --help
git status --short
```

Windows PowerShell 可使用：

```powershell theme={null}
New-Item -ItemType Directory -Force $env:TEMP\codex-rules-hooks-demo
Set-Location $env:TEMP\codex-rules-hooks-demo
git init
```

不要在示例中写真实令牌、SSH 私钥、Cookie、`.env` 内容或完整会话记录。命令 Hook 可能以当前用户权限运行，因此先读懂脚本，再在 `/hooks` 中信任项目配置。

| 需求             | 推荐机制                |
| -------------- | ------------------- |
| 固定命令前缀允许、询问或禁止 | Rules               |
| 修改后格式化或 lint   | `PostToolUse` Hook  |
| 调用前检查完整命令和工作目录 | `PreToolUse` Hook   |
| 会话开始注入项目状态     | `SessionStart` Hook |
| 记录调用、结果和耗时     | `PostToolUse` Hook  |
| 控制文件、网络和审批总边界  | `config.toml`、沙箱和审批 |

***

# 第一部分：Rules

## 1. Rules 的职责

Rules 针对 Codex 准备执行的命令，按参数前缀匹配后给出决策：

| 决策          | 含义 | 结果         |
| ----------- | -- | ---------- |
| `allow`     | 允许 | 不因这条规则额外询问 |
| `prompt`    | 询问 | 命中后要求人工确认  |
| `forbidden` | 禁止 | 直接拒绝，不运行命令 |

Rules 适合稳定、可审查的政策，例如允许只读的 `git status`，对 `gh pr merge` 保留确认，或禁止 `git push --force`。Rules 不负责格式化、记录日志或读取会话上下文。

沙箱、审批和 Rules 是不同层次：沙箱限制文件系统和网络，审批决定何时暂停，Rules 对命令给出更细的政策。一个 `allow` 不能突破其他安全层。

## 2. 规则文件位置

常见用户级规则文件：

```text theme={null}
~/.codex/rules/default.rules
```

项目级规则通常放在受信任项目的 `.codex/` 范围内。用户级规则影响多个项目；项目级规则只服务一个仓库，但其加载受项目可信状态和版本实现影响。不要把个人路径、用户名、令牌或机器专属地址提交到项目规则。

Rules 属于实验性能力时，目录、文件名和语法可能变化。先运行本机帮助确认加载方式，并在改动后重启或重新加载 Codex。

## 3. `prefix_rule` 基本写法

下面的规则要求匹配 `gh pr view` 前缀时询问：

```python theme={null}
prefix_rule(
    pattern = ["gh", "pr", "view"],
    decision = "prompt",
    justification = "查看 Pull Request 前确认目标仓库",
)
```

`pattern` 是按参数拆开的列表，不是任意文本搜索。它可以匹配：

```text theme={null}
gh pr view 123
gh pr view --repo example/project 123
gh pr view --json title
```

不会匹配：

```text theme={null}
gh pr list 123
gh pr --repo example/project view 123
```

可以用 `match` 和 `not_match` 写加载时的示例断言：

```python theme={null}
prefix_rule(
    pattern = ["make", "test"],
    decision = "allow",
    justification = "项目测试命令可自动运行",
    match = ["make test", "make test UNIT=1"],
    not_match = ["make clean"],
)
```

| 字段              | 作用                             |
| --------------- | ------------------------------ |
| `pattern`       | 必填或由版本规定的命令参数前缀                |
| `decision`      | `allow`、`prompt` 或 `forbidden` |
| `justification` | 向使用者说明政策原因                     |
| `match`         | 应命中的测试示例                       |
| `not_match`     | 不应命中的测试示例                      |

某些版本允许参数位置使用多个候选值。嵌套语法以当前 Rules 文档为准，优先选择窄前缀。

## 4. 如何选择决策

### `allow`

只对你理解且副作用可控的窄命令使用：

```python theme={null}
prefix_rule(
    pattern = ["git", "status"],
    decision = "allow",
    justification = "读取工作区状态",
)
```

不要为宽泛的 `bash`、`sh`、脚本解释器或任意 `git` 前缀设置 `allow`。后续参数可能改变副作用。

### `prompt`

适合访问网络、改变远端状态或目标需要人工确认的命令：

```python theme={null}
prefix_rule(
    pattern = ["gh", "pr", "merge"],
    decision = "prompt",
    justification = "合并 Pull Request 会改变远端状态",
)
```

### `forbidden`

适合组织或项目明确禁止的固定前缀：

```python theme={null}
prefix_rule(
    pattern = ["git", "push", "--force"],
    decision = "forbidden",
    justification = "禁止覆盖远端分支历史",
)
```

禁止规则越具体越容易审查。只拦一种拼写不等于覆盖所有别名、短参数、脚本封装和不同 Shell 写法。

多个规则同时命中时，按更严格的结果理解：

```text theme={null}
forbidden > prompt > allow
```

一条宽泛的 `allow` 不应绕过更具体的 `forbidden`。冲突要用检查命令验证，不要靠真实危险操作试错。

## 5. 复合命令的边界

对于可安全解析的简单线性命令链，Codex 可以拆分子命令分别评估，并采用更严格的结果：

```bash theme={null}
git status && rm -rf ./build
```

允许 `git status` 不代表后面的删除动作会被允许。Shell 包装也不能成为夹带危险命令的方式。

复杂 Shell 可能包含重定向、管道、命令替换、变量赋值、通配符、函数、循环或嵌套脚本：

```bash theme={null}
bash -lc 'TARGET="$HOME/project"; rm -rf "$TARGET"'
```

不要假设 Rules 能完整理解任意 Shell。对高风险操作组合使用沙箱、审批、最小权限和隔离环境；把复杂逻辑拆成可审查的命令，或由经过测试的 Hook 检查。

## 6. 用 `execpolicy check` 预检

改完 Rules 后先检查：

```bash theme={null}
codex execpolicy check --pretty \
  --rules ~/.codex/rules/default.rules \
  -- gh pr view 123 --json title
```

至少验证：

1. 应当 `allow` 的正常命令；
2. 应当 `prompt` 的外部写操作；
3. 应当 `forbidden` 的危险命令；
4. 前缀顺序、短参数和复合命令边界；
5. 命中的规则及其 `justification`。

示例：

```bash theme={null}
codex execpolicy check --pretty \
  --rules ~/.codex/rules/default.rules \
  -- git push --force origin main
```

验收规则时确认文件语法可解析、规则范围最小、没有秘密、项目团队已审阅，并且 `git diff` 只包含预期配置。

***

# 第二部分：Hooks

## 7. Hooks 的职责和生命周期

Hook 在固定事件发生时启动命令处理器。常见流程如下：

```text theme={null}
SessionStart
    |
UserPromptSubmit
    |
PreToolUse -> 工具执行 -> PostToolUse
    |                         |
    +------ 模型继续思考 ------+
                              |
                             Stop
```

| 事件                               | 触发时机      | 典型用途      | 能否阻止工具开始  |
| -------------------------------- | --------- | --------- | --------- |
| `SessionStart`                   | 启动、恢复或清理后 | 注入项目状态    | 不针对工具     |
| `UserPromptSubmit`               | 用户提交提示词时  | 检查或补充提示   | 依版本支持     |
| `PreToolUse`                     | 工具执行前     | 检查和拒绝调用   | 可以，但有覆盖限制 |
| `PostToolUse`                    | 工具执行后     | 格式化、审计、反馈 | 不能撤销副作用   |
| `Stop`                           | 准备结束本轮时   | 验收、要求继续   | 不能撤销工具动作  |
| `SubagentStart` / `SubagentStop` | 子代理起止     | 记录或验收     | 依版本支持     |
| `PreCompact` / `PostCompact`     | 上下文压缩前后   | 保存或恢复状态   | 不用于命令拦截   |
| `PermissionRequest`              | 即将申请权限时   | 处理审批请求    | 依版本支持     |

事件名称和字段可能变化。第一次使用只选一个事件，先确认触发，再增加其他处理器。

### `Pre`、`Post` 和 `Stop`

* `PreToolUse` 在工具前运行，适合拒绝；
* `PostToolUse` 在工具后运行，适合格式化、检查和反馈；
* `PostToolUse` 的阻断不能撤销已写入、删除、上传或执行的结果；
* `Stop` 的继续信号表示再处理一轮，不是回滚上一轮。

想表达“这条命令不要执行”，优先使用 Rules 或 `PreToolUse`。想表达“执行完成后检查”，使用 `PostToolUse`。

## 8. 配置位置和信任

常见位置：

| 位置                          | 生效范围      | 适合内容        |
| --------------------------- | --------- | ----------- |
| `~/.codex/hooks.json`       | 当前用户的所有项目 | 个人日志、工具链    |
| `~/.codex/config.toml`      | 当前用户的所有项目 | 内联用户级 Hooks |
| `<repo>/.codex/hooks.json`  | 当前项目      | 团队共享检查      |
| `<repo>/.codex/config.toml` | 当前项目      | 项目专属 Hooks  |

项目级 `.codex/` 通常只在项目可信时加载。项目 Hook 会随仓库传播，但它本质上是可执行代码，不能因为它位于 Git 仓库就自动信任。用户级配置也会叠加影响当前项目。

同一层同时使用 `hooks.json` 和内联 `[hooks]` 可能合并并产生警告。每一层最好选择一种写法，避免重复注册和顺序误判。

配置后在 Codex 中打开：

```text theme={null}
/hooks
```

逐条检查来源、事件、matcher、实际命令、解释器、路径、权限和信任状态。新增或修改定义后，基于哈希的信任状态可能需要重新审核。

某些版本提供 `--dangerously-bypass-hook-trust`。只有在来源已由构建系统审查且运行环境隔离时才考虑使用，不能用它掩盖“钩子不触发”的问题。

## 9. `hooks.json` 结构

最小的 `PostToolUse` 配置：

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/log_bash.py",
            "timeout": 30,
            "statusMessage": "记录 Bash 调用"
          }
        ]
      }
    ]
  }
}
```

三层结构是：事件、matcher 分组、动作数组。JSON 不允许尾逗号和 `//` 注释。

内联 TOML 写法：

```toml theme={null}
[[hooks.PostToolUse]]
matcher = "^Bash$"

[[hooks.PostToolUse.hooks]]
type = "command"
command = "python3 .codex/hooks/log_bash.py"
timeout = 30
statusMessage = "记录 Bash 调用"
```

常见处理器字段：

| 字段                | 作用               | 注意                |
| ----------------- | ---------------- | ----------------- |
| `type`            | 处理器类型            | 当前优先使用 `command`  |
| `command`         | Unix 类环境命令       | 使用固定解释器和可审查路径     |
| `command_windows` | Windows 覆盖命令，若支持 | 用 `py -3` 或绝对路径实测 |
| `timeout`         | 超时秒数             | 不要按毫秒填写           |
| `statusMessage`   | 执行状态文字           | 不泄露秘密             |
| `async`           | 异步选项             | 未支持时可能被跳过         |

命令工作目录通常是会话 `cwd`。相对路径依赖错误 cwd 时会失败；项目脚本可从 Git 根计算绝对路径，但 Windows、非 Git 目录和特殊 Shell 必须单独测试。

## 10. `matcher` 匹配规则

工具事件中的 `matcher` 通常是工具名称的正则表达式，而不是 glob：

| matcher              | 作用          |                   |          |
| -------------------- | ----------- | ----------------- | -------- |
| `^Bash$`             | 精确匹配 Bash   |                   |          |
| `^apply_patch$`      | 精确匹配文件补丁工具  |                   |          |
| \`^(Edit             | Write       | apply\_patch)\$\` | 匹配多个版本名称 |
| `^mcp__filesystem__` | 匹配一组 MCP 工具 |                   |          |
| 省略或空值                | 依版本行为匹配全部   |                   |          |

优先使用 `^` 和 `$`。文件修改工具可能显示为 `apply_patch`、`Edit` 或 `Write`，以实际 stdin 的 `tool_name` 为准，不要凭按钮名称猜测。

不同事件的 matcher 目标可能不同：`SessionStart` 可能匹配 `startup`、`resume`、`clear` 或 `compact`；`SubagentStop` 可能匹配代理类型；`Stop` 和 `UserPromptSubmit` 没有工具名，不能按 Bash 规则理解。

`PreToolUse` 不是覆盖所有执行路径的强制边界。复杂 Shell、特殊执行器、网页工具或其他 MCP 路径可能不经过你的 matcher。高风险命令仍需沙箱和审批。

同一事件匹配多个 Hook 时，不要依赖配置顺序或假设串行执行。共享文件要有锁和幂等设计；需要严格顺序时合并到一个脚本中。

***

# 第三部分：Hook 协议

## 11. stdin：事件 JSON

Codex 将事件 JSON 写入脚本的标准输入：

```text theme={null}
Codex 事件 -> stdin JSON -> 脚本
脚本退出码、stderr、stdout -> Codex
```

典型工具事件：

```json theme={null}
{
  "session_id": "session-example",
  "cwd": "/work/example",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git status --short"
  }
}
```

常见字段：

| 字段                | 用途             |
| ----------------- | -------------- |
| `session_id`      | 关联同一会话的事件      |
| `cwd`             | 确定脚本工作目录       |
| `hook_event_name` | 在一个脚本支持多个事件时分支 |
| `transcript_path` | 会话记录路径，涉及隐私    |
| `model`           | 记录模型，不应作为权限依据  |
| `permission_mode` | 当前权限模式，可能缺省    |
| `tool_name`       | 工具名称           |
| `tool_input`      | 工具输入对象         |

Bash 的命令通常是 `tool_input.command`，其他工具结构可能不同。健壮的读取方式：

```python theme={null}
import json
import sys

try:
    event = json.load(sys.stdin)
except json.JSONDecodeError as exc:
    print(f"invalid hook input: {exc}", file=sys.stderr)
    raise SystemExit(1)

name = event.get("tool_name", "")
tool_input = event.get("tool_input") or {}
command = tool_input.get("command", "") if isinstance(tool_input, dict) else ""
```

不要把用户输入直接拼成新的 Shell 字符串执行。脚本如需调用固定工具，应使用参数数组和明确的 `cwd`，避免二次 Shell 解析。

## 12. stdout、stderr 和退出码

一般可按以下方式理解：

| 信号       | 常见含义              |
| -------- | ----------------- |
| `exit 0` | 成功，继续正常流程         |
| `exit 2` | 特殊控制信号，含义依事件不同    |
| 其他非零     | 脚本失败或未定义错误        |
| stdout   | 协议 JSON 或事件允许的上下文 |
| stderr   | 人类可读错误、调试和阻断理由    |

在 `PreToolUse` 或某些提示提交事件中，`exit 2` 配合 stderr 理由通常表示拒绝：

```python theme={null}
print("拒绝：命令涉及生产环境", file=sys.stderr)
raise SystemExit(2)
```

在 `PostToolUse`、`Stop` 或 `SubagentStop` 中，动作已经完成或到了结束节点，`exit 2` 不能撤销副作用，通常表示反馈给模型重看或继续一轮。

结构化拒绝示例：

```json theme={null}
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "该命令会修改受保护目录"
  }
}
```

会话开始注入上下文：

```json theme={null}
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "本项目使用 pnpm，修改后先运行 pnpm test。"
  }
}
```

部分版本仍接受 `decision: "block"` 和 `reason` 等旧结构。不要把其他客户端的字段照抄进 Codex；按当前事件文档验证。

不要将调试文字写入要求 JSON 的 stdout。`SessionStart` 或 `UserPromptSubmit` 的纯文本在部分实现中会成为上下文，但 `Stop`、`SubagentStop` 可能要求严格 JSON。日志统一写 stderr 或独立文件。

## 13. 阻断的准确含义

“阻断”有三种不同情况：

1. **事前拒绝**：工具尚未运行，`PreToolUse` 拒绝调用。
2. **事后反馈**：工具已运行，`PostToolUse` 把检查结果交回模型。
3. **阻止结束**：`Stop` 要求 Codex 再处理一轮。

以下做法不能视为可靠阻断：

* 在 `PostToolUse` 打印“禁止执行”；
* 在 `PreToolUse` 只打印普通 stdout 文本；
* 使用当前版本不支持的 `continue: false`；
* 只拦 Bash 却假设其他工具路径也会被拦；
* 只依赖 Hook 而没有沙箱、审批和操作系统权限边界。

固定命令红线优先使用 `forbidden` Rules；需要判断工作目录、完整 JSON 或多个条件时才增加 `PreToolUse`。

***

# 第四部分：三个示例

## 14. 示例一：PostToolUse 记录 Bash

脚本 `.codex/hooks/log_bash.py`：

```python theme={null}
import json
import os
import sys
import time

try:
    event = json.load(sys.stdin)
except json.JSONDecodeError as exc:
    print(f"input error: {exc}", file=sys.stderr)
    raise SystemExit(1)

cwd = event.get("cwd") or os.getcwd()
log_path = os.path.join(cwd, ".codex", "hook-events.log")
os.makedirs(os.path.dirname(log_path), exist_ok=True)

record = {
    "time": time.time(),
    "event": event.get("hook_event_name"),
    "tool": event.get("tool_name"),
    "cwd": cwd,
}
with open(log_path, "a", encoding="utf-8") as log:
    log.write(json.dumps(record, ensure_ascii=False) + "\n")
```

配置：

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/log_bash.py",
            "timeout": 10,
            "statusMessage": "记录 Bash 事件"
          }
        ]
      }
    ]
  }
}
```

在 `/hooks` 中信任后，让 Codex 执行无害的 `pwd` 或 `git status`，检查日志是否生成。日志可能含路径或命令参数，不要提交到公共仓库，也不要默认记录完整提示词和工具输出。

## 15. 示例二：PostToolUse 自动格式化

格式化属于工具完成后的后处理：

```python theme={null}
import json
import os
import subprocess
import sys

try:
    event = json.load(sys.stdin)
    cwd = event.get("cwd") or os.getcwd()
    result = subprocess.run(
        ["ruff", "format", "."],
        cwd=cwd,
        capture_output=True,
        text=True,
        timeout=50,
    )
except Exception as exc:
    print(f"format hook failed: {exc}", file=sys.stderr)
    raise SystemExit(1)

if result.stdout:
    print(result.stdout, file=sys.stderr, end="")
if result.returncode:
    print(result.stderr, file=sys.stderr, end="")
    raise SystemExit(2)
```

注册：

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "^(Edit|Write|apply_patch)$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/format_changed.py",
            "command_windows": "py -3 .codex/hooks/format_changed.py",
            "timeout": 60,
            "statusMessage": "格式化文件修改"
          }
        ]
      }
    ]
  }
}
```

它不能阻止补丁写入；失败时应保留错误并反馈，不要假装成功。大型仓库应只处理受影响文件，并校验路径位于仓库内。

## 16. 示例三：PreToolUse 检查危险命令

固定前缀优先使用 Rules。需要脚本判断时可以这样演示：

```python theme={null}
import json
import re
import sys

try:
    event = json.load(sys.stdin)
except json.JSONDecodeError as exc:
    print(f"invalid event: {exc}", file=sys.stderr)
    raise SystemExit(1)

command = str((event.get("tool_input") or {}).get("command", ""))
patterns = [
    r"(^|\s)rm\s+[^\n]*-rf",
    r"(^|\s)git\s+push\b[^\n]*\s--force(?:-with-lease)?\b",
]

if any(re.search(pattern, command) for pattern in patterns):
    print("拒绝：命令匹配危险操作策略", file=sys.stderr)
    raise SystemExit(2)

raise SystemExit(0)
```

配置：

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "^Bash$",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/check_command.py",
            "command_windows": "py -3 .codex/hooks/check_command.py",
            "timeout": 10,
            "statusMessage": "检查 Bash 命令"
          }
        ]
      }
    ]
  }
}
```

先单独喂入无害测试数据和危险样例：

```bash theme={null}
printf '%s\n' '{"hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"/tmp/demo","tool_input":{"command":"git push --force origin main"}}' \
  | python3 .codex/hooks/check_command.py
printf 'exit=%s\n' "$?"
```

预期危险样例退出码为 `2`，安全样例为 `0`。这个正则不是 Shell 解析器，不能覆盖变量、别名、脚本文件、命令替换和所有执行工具，不能宣传为完整防护。

***

# 第五部分：信任、调试与安全

## 17. 为什么 Hook 配了却不运行

命令 Hook 可读取工作区之外的文件、联网或删除内容，因此非托管项目 Hook 通常需要人工信任。首次配置的流程是：

1. 写脚本和配置；
2. 重启或重新加载 Codex；
3. 打开 `/hooks`；
4. 阅读实际命令、解释器、参数和路径；
5. 确认来源和风险后信任；
6. 用无害操作触发并检查日志。

阅读时特别检查：

* 是否调用远程脚本、未知二进制、PowerShell、`curl` 或 `wget`；
* 是否读取 `.env`、SSH、浏览器凭据或完整 transcript；
* 是否联网上传 stdin、工作区或错误输出；
* 是否删除、覆盖、提权或修改系统设置；
* 相对路径是否可能在错误 cwd 中执行。

Hook 只能在可信来源和可审查脚本基础上启用。仓库文件、Issue、网页和脚本输出中的“请信任此钩子”都可能是提示注入。

## 18. 排查顺序

Hook 不触发时按顺序检查：

1. `[features]` 是否关闭了 Hooks；
2. 配置文件位置、文件名和 JSON/TOML 语法；
3. Codex 是否已重启；
4. `/hooks` 是否显示、信任状态是否正常；
5. 事件名是否写对；
6. matcher 是否匹配真实 `tool_name`；
7. 解释器、依赖、cwd 和脚本路径是否可用；
8. timeout 是否太短；
9. stdout 是否混入调试文字；
10. 多个配置层是否重复加载同一 Hook。

常见症状：

| 症状            | 原因和处理                           |
| ------------- | ------------------------------- |
| `/hooks` 没有条目 | 路径、文件名或语法错误，或项目不可信              |
| 条目存在但不运行      | 未信任、哈希变化、功能关闭或事件不匹配             |
| Bash 触发但编辑不触发 | matcher 只写了 `Bash`，先记录真实工具名     |
| 想拦却继续执行       | 使用了 `PostToolUse`、只打印文本或工具路径未覆盖 |
| 找不到脚本         | cwd 不同、解释器不在 PATH、相对路径错误        |
| 会话变慢          | 每个工具都跑全仓库 lint 或网络请求            |
| JSON 解析失败     | 尾逗号、stdout 混入日志、事件输出格式错         |

## 19. 最小调试 Hook

先确认事件是否到达，再加入业务判断：

```python theme={null}
import json
import os
import sys
import time

payload = json.load(sys.stdin)
cwd = payload.get("cwd") or os.getcwd()
path = os.path.join(cwd, ".codex", "hook-debug.log")
os.makedirs(os.path.dirname(path), exist_ok=True)

safe = {
    "time": time.time(),
    "event": payload.get("hook_event_name"),
    "tool": payload.get("tool_name"),
    "cwd": payload.get("cwd"),
}
with open(path, "a", encoding="utf-8") as log:
    log.write(json.dumps(safe, ensure_ascii=False) + "\n")
```

用管道单测：

```bash theme={null}
printf '%s\n' '{"hook_event_name":"PostToolUse","tool_name":"Bash","cwd":"/tmp/demo","tool_input":{"command":"git status"}}' \
  | python3 .codex/hooks/log_bash.py
```

检查正常输入返回 `0`、缺失字段可处理、违规输入返回 `2`、错误进入 stderr、stdout 仍是合法协议。调试日志必须脱敏，不要写完整命令、提示词或秘密。

## 20. 安全边界、失败策略与回滚

Rules 和 Hooks 是纵深防御的一层，不是唯一安全边界：

1. 用沙箱限制文件和网络范围；
2. 用审批策略控制出圈操作；
3. 用 Rules 固定命令政策；
4. 用 Hooks 做审计、检查和自动化；
5. 对高风险任务使用容器、虚拟机和最小权限账户。

不要在 Hook 中默认安装依赖、执行远程下载、读取密钥、访问生产数据库或把完整 transcript 发往外部服务。路径要拒绝 `..` 越界、空路径和符号链接逃逸；网络请求需有超时和域名白名单。

每个 Hook 都要明确失败策略：

```text theme={null}
正常退出：继续。
PreToolUse 检查违规：exit 2，拒绝调用。
脚本异常：写 stderr，并按项目政策拒绝或暂停。
PostToolUse 检查失败：不撤销副作用，反馈并人工复核。
Stop 反馈：继续一轮，但必须有明确终止条件。
```

回滚步骤：

1. 停止当前 Codex 会话；
2. 保存错误输出和配置副本；
3. 从 `hooks.json` 或 `config.toml` 删除对应事件块；
4. 确认脚本没有被其他项目使用后再移除；
5. 重启 Codex，在 `/hooks` 确认状态；
6. 检查工作区、日志和外部系统的既有副作用。

某些版本支持：

```toml theme={null}
[features]
hooks = false
```

关闭后需重启；它不会撤销已经执行过的命令，也不替代 Git、备份或外部系统回滚。不要删除整个配置目录来排查，因为可能一并删除登录信息、Rules 和其他用户配置。

## 21. 最终验收清单

* [ ] 已判断需求属于命令政策还是生命周期自动化。
* [ ] 配置放在正确的用户级或受信任项目级位置。
* [ ] Rules 的 `pattern` 是窄的参数前缀，并测试了 `match` / `not_match`。
* [ ] Rules 已用 `codex execpolicy check` 验证正常、边界和危险命令。
* [ ] Hook 事件和 matcher 匹配实际输入，不是界面猜测。
* [ ] 脚本能从 stdin 解析 JSON，并处理缺失字段。
* [ ] stdout 保持协议格式，调试信息写 stderr 或脱敏日志。
* [ ] `PreToolUse` 使用当前版本支持的拒绝信号。
* [ ] 没有把 `PostToolUse` 或 `Stop` 当成回滚机制。
* [ ] Hook 已在 `/hooks` 中审阅和信任。
* [ ] 已用无害命令验证触发、放行、拒绝和失败路径。
* [ ] timeout 有限，脚本不会无限等待网络或交互。
* [ ] `git diff --check` 通过，日志、缓存、密钥和临时产物未进入提交。

## 小结

* **Rules** 用 `.rules` 中的 `prefix_rule` 对命令前缀作 `allow`、`prompt`、`forbidden` 决策；更严格的结果优先，并可用 `execpolicy check` 预检。
* **Hooks** 用事件、`matcher` 和命令处理器连接生命周期；`PreToolUse` 适合事前拒绝，`PostToolUse` 适合事后检查，`Stop` 的阻断通常表示继续一轮。
* 脚本从 `stdin` 读取 JSON，用退出码表达粗粒度结果，用 `stdout` JSON 表达结构化控制；日志应放 stderr 或独立脱敏文件。
* 项目 Hook 是可执行代码，必须检查来源、权限、路径、网络和信任状态。
* 沙箱、审批、操作系统权限、隔离环境和人工审查仍是主要安全边界。

参考资料：`参考/codex/24-hooks.md`、`参考/codex/15-permissions.md`、`参考/codex/18-config.md`。动态字段、事件和命令行为以本机 Codex 版本及官方文档为准。
