第 18 讲:Apply Patch 补丁应用——先干跑,再决定放不放行

Codex 源码解析 · 第 18 讲

Codex 源码解析系列 第 18 讲:Apply Patch 补丁应用——先干跑,再决定放不放行 基于 OpenAI Codex 源码 · 2026-10-12 💡 本讲一句话: Codex 的 apply_patch 是一条"解析 → 干跑校验 → 安全裁决(自动放行/问用户/拒绝)→ 沙箱落盘"的四段流水线,外加一套容忍模型上下文行不精确的模糊匹配机制——即使部分写入失败,也会留下带 exact 标记的增量记录供回滚。读完你会明白:AI 改代码为什么可以"放心让它试错"。 一、为什么让模型"写补丁"而不是直接写文件? 第 17 讲我们看了 shell 命令怎么被卡在 execve 咽喉上。这一讲看另一种改代码的方式: 模型不直接写文件,而是输出一段补丁文本 —— *** Begin Patch ... *** End Patch 。为什么多此一举?因为"一次完整的、可校验的变更单元"比"一串散落的写操作"安全得多:补丁可以先 干跑验证 (文件在不在、行号对不对)、可以 整体审批 (一个弹窗覆盖所有目标文件)、失败时还能留下 精确的增量记录 用于回滚。 补丁格式由一组标记行定义,全部集中在 apply-patch/src/parser.rs (682 行)的头部: 📄 codex-rs/apply-patch/src/parser.rs (第 37-45、66-82 行) pub(crate) const BEGIN_PATCH_MARKER: &str = "*** Begin Patch"; // 补丁起始标记:模型输出的第一行必须是它,否则直接判非法 pub(crate) const END_PATCH_MARKER: &str = "*** End Patch"; // 补丁结束标记:最后一行必须是它(流式解析的 finish() 会强制检查) pub(crate) const ADD_FILE_MARKER: &str = "*** Add File: "; // 新增文件指令前缀,冒号后跟相对路径 pub(crate) const DELETE_FILE_MARKER: &str = "*** Delete File: "; // 删除文件指令前缀 pub(crate) const UPDATE_FILE_MARKER: &str = "*** Update File: "; // 修改文件指令前缀(最常用) pub(crate) const MOVE_TO_MARKER: &str = "*** Move to: "; // 重命名目标路径,出现在 UpdateFile hunk 内部 pub(crate) const CHANGE_CONTEXT_MARKER: &str = "@@ "; // chunk 定位上下文行:通常是一个函数/类定义,用来缩小搜索范围 pub enum Hunk { // 一个补丁块 = 对单个文件的一种操作;整个补丁是 Hunk 的有序列表 AddFile { path: PathBuf, contents: String }, // 新增文件:路径 + 完整内容(覆盖已有文件也算 Add) DeleteFile { path: PathBuf }, // 删除文件:只需要路径,内容在验证阶段从磁盘读取 UpdateFile { path: PathBuf, move_path: Option<PathBuf>, chunks: Vec<UpdateFileChunk> }, // 修改/重命名:move_path 非空即 rename;chunks 必须按文件内先后顺序排列 } 为什么这样设计: 注意 UpdateFile 的 chunks 注释——"chunks should be in order"。这不是文档洁癖:后面的模糊匹配算法(第八节)依赖 chunk 顺序单调推进搜索起点,乱序会导致定位错行。而 AddFile 携带完整内容而非 diff,是因为"新增文件没有旧版本可对齐"——用全量文本最不容易出错。 补丁有两个入口:模型直接调用 apply_patch 工具( ToolPayload::Custom ),或者在 shell 里跑 apply_patch <<'EOF'…

阅读全文:Codex 源码解析 第 18 讲