Featured image of post Codex Rules 实用指南:用 prefix_rule 减少重复授权

Codex Rules 实用指南:用 prefix_rule 减少重复授权

从 Sandbox、审批策略与 Rules 的关系讲起,介绍 prefix_rule 的 allow、prompt、forbidden、参数前缀匹配、内联测试和 execpolicy check,并用 Git 与 GitHub CLI 说明如何在减少重复授权的同时保留安全边界。

-- 次浏览
-- 条评论

使用 Codex 处理 Git 提交、查看 Pull Request 或推送分支时,经常会遇到同一种打断:任务本身已经明确,命令也重复执行过很多次,但每次离开沙箱仍要重新确认。

Codex Rules 可以按命令前缀决定这类请求是自动允许、继续询问还是直接禁止。不过,Rules 不是简单的命令白名单。它只负责“命令级审批决策”,不会理解用户是否真的授权了这次提交,也不会替代 Sandbox、approval_policyAGENTS.md

如果想先横向了解 Claude Code、Codex 与 OpenCode 的权限模型,可以配合 AI 编程 CLI 的权限怎么配 一起阅读。本文只讨论 Codex Rules 的配置、验证与安全边界。

写在前面

这篇文章来自一次真实的配置过程。目标很简单:让 Codex 在已经获得用户明确授权后,可以顺畅完成下面这些常规操作,而不是每一步都再次弹出权限请求。

  • 显式暂存指定文件
  • 创建普通 Git 提交
  • 推送 mainmaster
  • 使用 gh pr viewgh run view 等命令读取 GitHub 状态

最初很容易把问题理解成“把这些命令都设为 allow 就行”。实际验证后会发现,prefix_rule 匹配的是参数前缀,而不是一条带结束边界的完整命令。规则写得过宽,尾随的 --force--amend 或其他参数也可能一起被放行。

本文示例使用 codex-cli 0.144.1 验证。OpenAI 官方目前仍将 Rules 标记为实验性功能,后续版本可能调整语法或加载行为,正式使用前应再次核对 Codex Rules 官方文档

先说结论

写 Codex Rules 时,建议先记住下面几条:

  • allowpromptforbidden 控制的是命令申请在沙箱外执行时的处理方式。
  • Rules 不会扩大 Sandbox 的文件系统或网络权限,也不会替代 approval_policy
  • pattern 是参数列表的精确前缀,不是 Shell 字符串,也没有“匹配到命令结尾”的含义。
  • matchnot_match 是规则加载时执行的内联测试,不是运行时的允许和拒绝列表。
  • 多条规则同时匹配时,最严格的结果生效:forbidden > prompt > allow
  • 只读、作用明确的命令更适合 allow;写操作需要结合 AGENTS.md、Skill 或包装脚本约束调用条件。
  • 不要宽泛放行 bashpythongitgh 这类可以承载大量不同操作的命令前缀。

Rules 在 Codex 权限体系中的位置

Codex 本地权限不是由一份规则文件单独决定的,而是由多个层次共同形成边界。

控制层解决的问题常见配置
Sandbox命令在技术上可以读写哪些路径、能否访问网络sandbox_mode = "workspace-write"
Approval policy哪些类型的操作需要暂停并申请批准approval_policy = "on-request"
Rules某个命令前缀离开沙箱时应允许、询问还是禁止prefix_rule(... decision = "allow")
AGENTS.md / Skill什么条件下应该执行命令,以及执行前后要做哪些检查“用户明确要求后才能提交或推送”

OpenAI 官方将 Sandbox 和 Approval policy 定义为两个协同工作的安全层:前者限制“技术上能做什么”,后者决定“什么时候需要询问”。Rules 则进一步处理命令级别的审批结果,详见 Agent approvals & security

这里最容易忽略的是:Rules 不理解对话中的业务意图。

假设存在下面的规则:

1
2
3
4
prefix_rule(
    pattern = ["git", "push", "origin", "main"],
    decision = "allow",
)

它表达的是:当 Codex 申请在沙箱外运行以这组参数开头的命令时,不再弹出批准请求。它并没有表达“只有用户刚刚说了推送才能执行”。

因此,更完整的职责划分应该是:

  • AGENTS.md 规定“没有用户明确要求,不主动提交或推送”。
  • Git Skill 负责检查工作区、提交身份、待提交 diff 和上游状态。
  • Rules 只负责在这些前置条件已经满足时减少重复批准。

justification 也只是可展示给用户的说明文字,不是 Codex 在运行时执行的条件判断。

创建 Rules 文件

用户级规则通常放在:

1
~/.codex/rules/default.rules

也可以拆成多个职责更清晰的文件:

1
2
3
~/.codex/rules/
├── git-commit.rules
└── github-readonly.rules

项目级规则可以放在:

1
<repo>/.codex/rules/project.rules

Codex 会在启动时扫描活动配置层旁边的 rules/ 目录。项目级 .codex/ 配置层只有在项目被信任后才会加载;用户级和系统级配置仍可正常生效。配置层关系可以参考 Codex Config basics

新增或修改 .rules 后,需要重新启动 Codex。通过 TUI 把命令加入允许列表时,Codex 默认会把生成的规则写入:

1
~/.codex/rules/default.rules

.rules 使用 Starlark 语法。它看起来接近 Python,但规则引擎会以无副作用的方式执行,不允许规则代码随意读写文件或调用外部程序。

从一条最小规则开始

下面的规则表示:当 Codex 想在沙箱外执行 gh pr view 时,每次仍然询问用户。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
prefix_rule(
    pattern = ["gh", "pr", "view"],
    decision = "prompt",
    justification = "查看 Pull Request 前仍然请求用户确认。",
    match = [
        "gh pr view 123",
        "gh pr view --repo owner/repo",
        "gh pr view 123 --json title,state,author",
    ],
    not_match = [
        "gh pr --repo owner/repo view 123",
        "gh pr create",
    ],
)

prefix_rule() 当前支持下面这些字段:

字段是否必填作用
pattern定义需要匹配的命令参数前缀
decision匹配后的决策,默认是 allow
justification给人阅读的规则理由,可能显示在批准或拒绝信息中
match应该命中当前规则的测试命令
not_match不应该命中当前规则的测试命令

建议始终显式写出 decision。虽然它默认为 allow,但权限规则不适合依赖不明显的默认值。

allow、prompt 和 forbidden 的区别

decision匹配后的行为适用场景
allow允许命令在沙箱外执行,不再请求批准风险边界清晰、参数稳定的重复操作
prompt每次匹配仍请求批准有副作用、需要保留人工判断的命令
forbidden直接阻止,不再询问明确不希望 Agent 执行的危险操作

例如,可以允许只读的仓库信息查询:

1
2
3
4
5
prefix_rule(
    pattern = ["gh", "repo", "view"],
    decision = "allow",
    justification = "允许读取 GitHub 仓库基本信息。",
)

对推送操作采用更保守的策略:

1
2
3
4
5
prefix_rule(
    pattern = ["git", "push", "origin", ["main", "master"]],
    decision = "prompt",
    justification = "推送会修改远程仓库,每次执行前都需要确认。",
)

明确禁止常见的破坏性恢复操作:

1
2
3
4
5
prefix_rule(
    pattern = ["git", "reset", "--hard"],
    decision = "forbidden",
    justification = "禁止直接覆盖工作区,请先检查 diff 并使用非破坏性的恢复方式。",
)

如果一条命令同时命中多条规则,Codex 不按文件顺序选择最后一条,而是采用最严格的结果:

1
forbidden > prompt > allow

因此,可以在已有 allow 旁边增加更具体的 promptforbidden。不过,这仍然受参数位置和前缀匹配能力限制,不能把它当成完整的命令语法解析器。

pattern 匹配的是参数前缀

执行下面的命令时:

1
gh pr view 123 --json title,state

Codex 会按照类似 execvp(3) 的方式,把它理解为参数列表:

1
["gh", "pr", "view", "123", "--json", "title,state"]

因此,下面的规则可以匹配它:

1
pattern = ["gh", "pr", "view"]

但参数位置发生变化后就不能匹配:

1
gh pr --repo owner/repo view 123

因为它的前三个参数已经变成:

1
["gh", "pr", "--repo"]

pattern 中的嵌套列表表示某个参数位置可以选择多个字面值:

1
pattern = ["gh", "run", ["list", "view"]]

它可以匹配:

1
2
gh run list
gh run view 123456

但不会匹配:

1
gh run cancel 123456

尾随参数是最容易忽略的风险

前缀匹配不要求命令在 pattern 结束的位置终止。下面这条规则:

1
2
3
4
prefix_rule(
    pattern = ["git", "push", "origin", "master"],
    decision = "allow",
)

不仅会命中:

1
git push origin master

codex-cli 0.144.1 中,它也会命中:

1
git push origin master --force

同样,下面的规则:

1
pattern = ["git", "commit", "-m"]

会同时匹配:

1
2
git commit -m "更新文档"
git commit -m "更新文档" --amend

这说明 Rules 当前没有“只允许这些参数,而且命令必须在这里结束”的表达方式。not_match 也不能补上这个缺口,因为它只是规则加载测试,不参与运行时拒绝。

面对这种情况,可以根据风险要求选择三种方案:

  • 接受前缀边界,并通过 AGENTS.md 和 Git Skill 约束 Codex 只能在用户明确授权、完成预检后调用。
  • 把带副作用的原生命令设为 prompt,保留最后一次人工确认。
  • 编写参数受限的包装脚本,由脚本验证分支、参数和工作区状态,再只允许调用该脚本。

如果无法接受尾随参数被一并匹配,就不应该直接把原始 git pushgit commit 前缀设为 allow

match 和 not_match 只是内联测试

一条完整规则最好同时提供正常样例和反例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
prefix_rule(
    pattern = ["gh", "issue", "view"],
    decision = "allow",
    match = [
        "gh issue view 123",
        "gh issue view 123 --comments",
    ],
    not_match = [
        "gh issue create",
        "gh issue close 123",
    ],
)

Codex 加载文件时会验证:

  • match 中的命令必须命中当前规则。
  • not_match 中的命令不能命中当前规则。

如果测试与 pattern 冲突,规则文件会加载失败,从而尽早暴露配置错误。

需要注意,下面这种写法并不会在运行时禁止强推:

1
2
3
not_match = [
    "git push --force origin main",
]

它只验证“这条命令不会命中当前这一个 prefix_rule”。如果它命中了另一个 allow,或者没有匹配任何规则,后续仍由其他 Rules、审批策略和 Sandbox 决定。

使用 execpolicy check 验证规则

不要只靠肉眼审阅 .rules。Codex 提供了 execpolicy check,可以查看命令最终命中了哪些规则。

1
2
3
codex execpolicy check --pretty \
  --rules ~/.codex/rules/github-readonly.rules \
  -- gh pr view 123 --json title,state,author

匹配成功时,输出会包含最终决策、命中的前缀和 justification。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "matchedRules": [
    {
      "prefixRuleMatch": {
        "matchedPrefix": ["gh", "pr", "view"],
        "decision": "allow",
        "justification": "允许只读查看 Pull Request。"
      }
    }
  ],
  "decision": "allow"
}

多个文件可以重复传入 --rules

1
2
3
4
codex execpolicy check --pretty \
  --rules ~/.codex/rules/git-commit.rules \
  --rules ~/.codex/rules/github-readonly.rules \
  -- gh run view 123456 --log

每条规则至少应该测试:

  • 最短的预期命令
  • 带常用参数的预期命令
  • 容易混淆的相邻子命令
  • 参数位置发生变化的命令
  • 在规则前缀后追加高风险参数的命令

没有匹配到规则不等于 forbidden。这只表示当前传入的 Rules 没有给出决策,实际执行时还会继续受其他配置层、审批策略和 Sandbox 影响。

Shell 包装与复合命令如何匹配

Agent 执行命令时,外层工具有时会把多个操作包装成:

1
["bash", "-lc", "git add README.md && rm -rf /tmp/example"]

对于只包含普通单词,并使用 &&||;| 连接的线性命令,Codex 会尝试拆分后逐个应用 Rules。上面的脚本会被分别检查为:

1
2
git add README.md
rm -rf /tmp/example

整个调用采用最严格的决策。因此,即使 git addallow,后面的命令仍可能让整体请求变成 promptforbidden

如果脚本包含下面这些高级 Shell 特性,Codex 不会尝试拆分:

  • 输出或输入重定向:>>><
  • 命令替换:$(...) 或反引号
  • 环境变量赋值或展开
  • *? 等通配符
  • iffor 等控制流

这时 Rules 看到的可能只是:

1
["bash", "-lc", "<完整脚本>"]

因此不要设置下面这种宽泛规则:

1
2
3
4
prefix_rule(
    pattern = ["bash", "-lc"],
    decision = "allow",
)

它会让任意复杂 Shell 脚本都有机会绕过逐条命令确认。官方对拆分条件和保守回退行为有更完整的说明,可以参考 Shell wrappers and compound commands

实际案例:GitHub CLI 只读规则

只读、子命令明确的 GitHub CLI 操作比较适合自动允许:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
prefix_rule(
    pattern = ["gh", "pr", "view"],
    decision = "allow",
    justification = "允许只读查看 Pull Request。",
    match = [
        "gh pr view 123",
        "gh pr view 123 --json title,state,author",
    ],
    not_match = [
        "gh pr create",
        "gh pr merge 123",
    ],
)

prefix_rule(
    pattern = ["gh", "run", ["list", "view"]],
    decision = "allow",
    justification = "允许读取 GitHub Actions 运行状态和日志。",
    match = [
        "gh run list",
        "gh run view 123456 --log",
    ],
    not_match = [
        "gh run cancel 123456",
        "gh run rerun 123456",
        "gh workflow run deploy.yml",
    ],
)

不建议直接允许:

1
pattern = ["gh"]

因为 gh 除了读取信息,还可以创建、合并、关闭、删除、触发工作流,甚至通过 gh api 发起自定义 API 请求。

实际案例:Git 提交流程怎样取舍

Git 写操作比 GitHub 只读查询更难定义安全边界。下面是一种以效率为优先的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
prefix_rule(
    pattern = ["git", "add"],
    decision = "allow",
    justification = "允许在用户要求提交时显式暂存文件。",
)

prefix_rule(
    pattern = ["git", "commit", "-m"],
    decision = "allow",
    justification = "允许使用明确提交信息创建普通提交。",
)

prefix_rule(
    pattern = ["git", "push", "origin", ["main", "master"]],
    decision = "allow",
    justification = "允许在用户明确要求后推送主分支。",
)

它能显著减少常规提交流程中的重复确认,但 Rules 本身无法保证这些文字条件真的成立,而且还存在尾随参数问题。因此只有同时满足下面的约束时,才适合采用这种配置:

  • AGENTS.md 明确规定不主动提交和推送。
  • Git Skill 要求先检查工作区、提交内容、作者邮箱和上游状态。
  • Skill 使用显式文件路径暂存,不使用 git add .git add -A
  • 不自动 --amend、重写历史或强推。
  • 用户理解 Rules 是对 Agent 工作流的信任,不是命令级的绝对隔离。

如果更看重严格边界,可以把 commitpush 改为 prompt,或者只允许一个经过参数校验的包装脚本。

常见的破坏性操作可以保留为 forbidden,但要知道前缀规则很难覆盖所有参数排列:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
prefix_rule(
    pattern = ["git", "reset", "--hard"],
    decision = "forbidden",
    justification = "禁止覆盖工作区,请先检查 diff。",
)

prefix_rule(
    pattern = ["git", "clean", ["-fd", "-df"]],
    decision = "forbidden",
    justification = "禁止批量删除未跟踪文件。",
)

不要直接使用:

1
pattern = ["git"]

否则普通查询、提交、配置修改、历史重写和强推都会落入同一个宽泛规则。

哪些命令不适合整类 allow

下面这些前缀能够承载任意代码、复杂脚本或大量外部副作用,通常不应该只按第一个参数自动允许:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
bash
sh
zsh
python
node
ruby
perl
git
gh
curl
docker
kubectl
terraform
ansible
make
npm run

这不是说它们永远不能配置,而是应该继续细分到稳定的子命令和参数位置。例如允许 gh repo view,而不是允许整个 gh;允许固定的检查脚本,而不是允许整个 python

把 Rules 同步到另一台电脑

用户级 Rules 默认位于 ~/.codex/rules/,不适合直接把整个 ~/.codex 加入版本控制,因为其中还可能包含认证、日志、历史和状态数据库。

一种更清晰的做法是建立自己的私有配置仓库。下面使用 agent-toolkit 作为本地目录示意,它不是一个可供读者直接克隆的公开模板仓库:

1
2
3
4
5
6
7
8
9
~/.agent-toolkit/
├── codex/
│   └── rules/
│       ├── git-commit.rules
│       └── github-readonly.rules
├── skills/
├── claude/
└── scripts/
    └── install.py

再从 Codex 的实际加载目录创建软链接:

1
2
~/.codex/rules/git-commit.rules
    -> ~/.agent-toolkit/codex/rules/git-commit.rules

先在自己使用的 Git 托管平台创建私有仓库,再在另一台已经配置访问权限的电脑上克隆并运行安装器:

1
2
3
4
git clone git@github.com:<your-account>/<your-private-repo>.git ~/.agent-toolkit
cd ~/.agent-toolkit
python3 scripts/install.py --dry-run
python3 scripts/install.py

这里的仓库地址、目录结构和 install.py 都只是实现思路。读者需要根据自己的 Codex Rules、Skills 和 Claude Code 配置编写对应的私有仓库与安装脚本,不能假设存在一个公开的 agent-toolkit 项目可以直接安装。

这样可以同时获得:

  • Git 版本历史和代码审阅
  • Codex 与 Claude Code 共用的 Skills
  • Codex 专属 Rules
  • 冲突检测和可重复安装
  • 多电脑一致的目录结构

即使仓库是私有的,也只应保存规则、Skill、脚本和不含敏感信息的说明,不应提交 Token、Cookie、认证文件或运行时状态。私有仓库能降低意外公开的概率,但不能替代凭证管理和提交前检查。

常见误区

把 not_match 当成 deny

not_match 只验证当前规则不应该命中某条命令。真正阻止命令必须使用 decision = "forbidden",或者依靠审批策略和 Sandbox。

认为 justification 会限制执行条件

justification = "仅在用户授权后推送" 不会让 Codex 自动检查用户是否说过这句话。语义条件应该写进 AGENTS.md 或 Skill。

认为 pattern 会匹配到命令结尾

["git", "push", "origin", "main"] 仍可能匹配后面带有额外参数的命令。为所有 allow 样例追加高风险参数进行测试,是 Rules 审阅中不可省略的一步。

认为没有匹配就是禁止

execpolicy check 没有返回匹配规则,只代表当前规则文件没有决策。实际执行还会继续经过其他配置层、审批策略和 Sandbox。

修改规则后不重启 Codex

Codex 在启动时扫描 Rules。修改文件后,应新开会话再观察真实审批行为,不能只依赖旧会话。

在未信任项目中依赖项目级规则

未信任项目会跳过项目内的 .codex/ 配置层,包括项目级 Rules。需要跨仓库稳定生效的个人规则更适合放在用户级配置层。

总结

Codex Rules 最适合解决“命令稳定、频繁执行、每次批准价值很低”的重复授权问题。它的优势是语法简单、可以内置测试,并能与用户级、项目级和团队配置层结合。

prefix_rule 只是参数前缀匹配器,不是完整的命令安全策略。allow 既不会理解用户意图,也不会自动拒绝尾随的危险参数。真正可靠的实践应该把职责拆开:

  • Sandbox 限制命令技术上能访问的资源。
  • Approval policy 决定哪些操作需要批准。
  • Rules 减少稳定命令的重复询问。
  • AGENTS.md 规定什么时候可以执行。
  • Skill 或包装脚本负责提交前检查和参数约束。

先用 codex execpolicy check 验证每一条正常与异常命令,再逐步扩大 allow 范围,比一次性放行整个工具更安全,也更容易维护。