[控场AI]
· 12 分钟阅读· 6,458 字

Claude Code、Codex与Aider编辑协议对比:精确替换、补丁与SEARCH/REPLACE

Claude Code、Codex与Aider编辑协议对比:精确替换、补丁与SEARCH/REPLACE

对比 Claude Code、Codex 与 Aider 的编辑协议,揭示重复行、陈旧上下文与空白漂移下的可靠性差异。

文章拆解了 Claude Code、Codex 与 Aider 三家 AI 编程代理的代码编辑协议,以同一段含重复 timeout 赋值的 Python 文件为对照样本。Claude Code 采用精确字符串替换,要求 old_string 唯一匹配且文件已读,重复行直接拒绝;Codex 的 apply_patch 不检查唯一性,裸 @@ 会改首个匹配,需加锚点定位;Aider 的 diff 格式同样取首个匹配,仅在使用省略号且片段重复时拒绝。三者对陈旧上下文和空白漂移的处理也各不相同:Claude Code 严格,Codex 四轮宽容匹配,Aider 折中。文章最后讨论撤销能力各家只管自己,并给出实践建议:检查 diff 落点、保留按轮快照。

AI 编程代理真正落地写代码时,决定它可靠性的往往不是模型多聪明,而是它用哪种格式描述「改这一行」,以及本机那段应用代码如何处理对不上的情况。同一份配置文件里两个函数都写了 timeout = 30,你让代理把生产环境改成 60——有的代理会拒绝并要求补上下文,有的则直接改了第一处(开发环境那处),补丁还干净地应用成功。

这篇文章拆解 Claude Code、Codex 与 Aider 三家的编辑协议,全部结论来自各自官方文档与开源实现,并以同一段 Python 文件作为对照样本:

def load_dev():
    timeout = 30
    retries = 3

def load_prod():
    timeout = 30
    retries = 5

Claude Code、Codex 与 Aider 编辑协议对比

三种「改这一行」的表达方式

Claude Code:精确字符串替换

Claude Code 的 Edit 工具接收 old_string 与 new_string,文档明确写着「不使用正则或模糊匹配」。三道检查必须通过:Claude 已读过该文件、old_string 与文件中文本完全一致、且只出现一次(除非设置 replace_all: true)。读文件这条规则对新模型有所放宽——Claude Opus 4.6、Claude Haiku 4.5 及更早模型始终要求先读,而 Claude Code v2.1.208 之后的新模型在 Read 工具可用且读取不需要权限确认时可以跳过。

对应上面任务的字段写法是:

Edit
  old_string: "    timeout = 30"
  new_string: "    timeout = 60"
  replace_all: false

Codex:补丁信封(patch envelope)

OpenAI 的 Codex Prompting Guide 建议单文件编辑使用 apply_patch,称模型「已针对这种 diff 格式训练」,并强烈推荐使用 OpenAI 自己的实现。该实现开源于 codex-rs/apply-patch crate,标记包括 *** Begin Patch、*** Add File:、*** Delete File:、*** Update File:、*** Move to:、*** End of File、@@ 与 *** End Patch。变更行以 - 或 + 开头,上下文行以空格开头,不带行号:

*** Begin Patch
*** Update File: config.py
@@
-    timeout = 30
+    timeout = 60
*** End Patch

apply_patch 是 OpenAI 为代码编辑场景设计的专用工具格式,模型通过输出这种结构化文本块来声明对文件的增删改操作,而不是直接重写文件内容。它类似于 Unix 的 patch 命令,但语法更简化、去除了行号依赖,便于语言模型稳定生成。OpenAI 将其开源为 codex-rs/apply-patch crate,并强烈建议使用官方实现,因为不同实现可能对边界情况(如上下文行匹配、文件末尾处理)有细微差异,进而影响补丁的应用结果。

该协议的标记设计将「文件操作」与「行变更」分离:*** Update File: 声明目标文件,@@ 引入变更块,*** End Patch 收尾。变更行以 -/+ 开头、上下文行以空格开头,不带行号,这降低了模型数错行的风险,但也意味着应用器必须依赖文本内容本身来定位,由此引出后文讨论的重复行与空白漂移问题。

Aider:格式由你选

Aider 的 edit formats 包括 whole(模型返回整个文件)、diff(SEARCH/REPLACE 块)、diff-fenced(路径写在围栏内,主要面向 Gemini 模型)、udiff(简化版 unified diff),以及 architect 模式下的两个 editor- 变体。diff 块看起来像合并冲突:

config.py
```python
<<<<<<< SEARCH
    timeout = 30
=======
    timeout = 60
>>>>>>> REPLACE

Aider 还公开了各模型格式正确率。其 polyglot 排行榜(最近更新于 2025 年 11 月 20 日)在 225 道题通过率旁列出「Correct edit format」一列:gpt-5 (high) 以 88.0% 正确率居首,使用 `diff` 时格式正确率为 91.6%。即便是榜首模型也会发出格式错误的编辑,应用层必须逐一捕获。


Aider 的 `diff` 格式中的「SEARCH/REPLACE 块」是一种显式的块替换语法:模型输出一个 `<<<<<<< SEARCH` 区段(要查找的原始代码)和一个 `>>>>>>> REPLACE` 区段(替换后的代码),应用器在文件中定位 SEARCH 内容并整体替换为 REPLACE 内容。它不依赖行号,也不要求输出完整文件,因此在长文件中只需给出局部上下文即可完成编辑。

`diff-fenced` 是 `diff` 的变体,区别在于文件路径写在代码围栏内部,主要面向 Gemini 模型,因为该模型对围栏格式的遵循度更好。`udiff` 则是简化版 unified diff,保留 `+`/`-` 行标记但省略行号和多数元数据,旨在减少模型用注释省略代码的倾向。`whole` 格式要求模型返回整个文件内容,应用时直接覆盖,没有匹配失败的风险,但代价是 token 消耗大且可能覆盖并发变更。`editor-` 变体则用于 architect 模式:一个模型负责规划,另一个模型负责生成实际编辑。
## 重复行:一家拒绝,两家取首个匹配

面对重复的 `timeout = 30`,Claude Code 的唯一性检查直接失败。文档说明 Claude 会「提供更长的字符串并带上足够上下文锁定其中一处,或设置 `replace_all: true` 全部替换」。能成功的重试必须带上函数头:

Edit old_string: "def load_prod():\n timeout = 30" new_string: "def load_prod():\n timeout = 60" replace_all: false


Codex 的应用器不检查唯一性。在 `file_update.rs` 中,每个块的旧行从当前位置向前搜索,`seek_sequence.rs` 返回第一个匹配窗口,不检查后续是否还有匹配。用上面那个裸 `@@` 补丁,第一个 `timeout = 30` 位于 `load_dev`,被改掉的就是它。

加锚点可以修正。`@@` 之后的文本是变更上下文:应用器先找到那一行,之后才搜索旧行:

*** Begin Patch *** Update File: config.py @@ def load_prod():

  • timeout = 30
  • timeout = 60 *** End Patch

Aider 的 `diff` 格式在此与 Codex 类似。`editblock_coder.py` 中的 `perfect_replace` 替换第一个匹配块后返回。只有一条路径更严格:当 SEARCH 块用 `...` 省略行时,如果该片段出现多次,Aider 会拒绝。

公平地说,模型很少只发一行裸文本,补丁里的上下文行和多行 SEARCH 块通常足以让匹配唯一。差别只在它们不够时:Claude Code 停下,另外两家在首个匹配处应用。

## 陈旧上下文:模型看过之后文件变了

当你在编辑、格式化工具运行,或第二个代理碰了同一个文件,模型手里的副本就旧了。

Claude Code 对此有明确规则。自 v2.1.208 起,如果文件在 Claude 上次读取后发生了变化,但 `old_string` 仍能「精确且无歧义」匹配当前内容,且读取不需要权限确认,编辑会应用,结果中会提示文件还有其他变更,让 Claude 在依赖上下文代码的编辑前重新读取。其他情况(例如 `old_string` 已过期),Claude 会重新读取文件再编辑。v2.1.208 之前,对读取后磁盘上已变化的文件,它一律拒绝编辑。

Codex 会返回模型能据以行动的错误信息。如果找不到 `@@` 行,`file_update.rs` 返回 `Failed to find context '<line>' in <path>`;如果找不到旧行,返回 `Failed to find expected lines in <path>:` 并附上它要找的行。API 指南对自建 harness 描述了同样的循环:报告 `status: "failed"` 并附带简短的人类可读消息。

Aider 返回 `SearchReplaceNoExactMatch: This SEARCH block failed to exactly match lines in <path>`,并提醒「SEARCH 区必须与已有行块完全一致,包括所有空白、注释、缩进、docstring 等」。如果失配持续出现,其排障页建议改用 `--edit-format whole`。

`whole` 格式不会失配,因为没有东西可匹配。但如果模型手里的副本是旧的,这次重写会把旧版本覆盖到之后的所有变更之上,且不报任何错误。另一个风险是省略。edit-formats 页面提到 `udiff` 主要与 GPT-4 Turbo 配合使用,因为能减少该模型用「original code here」之类注释省略代码的习惯。在 Aider 的 unified diffs 文章中,把 GPT-4 Turbo 从 SEARCH/REPLACE 切到 `udiff`,其 laziness 基准分数从 20% 提高到 61%。

## 空白漂移:严格、宽容与折中

模型是重新敲一遍它想替换的文本,重敲就会漂移:制表符变成四个空格、尾部空白消失、直引号变弯引号。

Claude Code 刻意严格。文档称「一个字符的空白或缩进差异都足以导致失配」。失配时编辑不应用。

Codex 的应用器是三家里最宽容的。`seek_sequence` 做四轮匹配,其注释称之为「逐级降低严格度」:精确匹配;忽略尾随空白;两侧都 trim;把常见 Unicode 标点(破折号、引号)规范化为 ASCII。旧行中的缩进漂移仍能找到目标。代价在完全不匹配时显现:更宽松的轮次可能在更多位置命中,而第一个找到的窗口就胜出。

Aider 处于中间。它先精确匹配,再容忍缺失的前导空白,然后去掉多余的首个空行重试。同一文件中存在模糊编辑距离匹配器,但调用前有一个裸 `return`,所以永远不会执行。

宽容有回报。同一篇 unified diffs 文章报告,关闭 flexible patching 会产生「9 倍的编辑错误增长」。

| 失败场景 | Claude Code Edit | Codex apply_patch | Aider diff |
| --- | --- | --- | --- |
| 重复匹配 | 拒绝 | 锚点后首个匹配 | 首个匹配 |
| 陈旧文本 | 仍精确则应用,否则重读 | 向模型报错 | 向模型报错 |
| 空白漂移 | 失配 | 四轮宽容匹配 | 容忍前导空白 |


「空白漂移」指模型在重新输出待替换文本时,对空白字符(空格、制表符、换行)产生了与原文件不一致的表达。由于语言模型逐 token 生成文本,它并不「感知」原始文件的字节级空白,而是根据上下文重建,因此制表符与空格的混用、行尾多余空格的有无、甚至引号字符的弯直变化都可能出现。这类差异对人类读者几乎不可见,但对基于字符串精确匹配的编辑协议是致命的。

不同协议对空白漂移的容忍度直接决定了编辑成功率与误匹配风险的平衡:严格匹配(如 Claude Code)保证不会改错位置,但会频繁失配;宽容匹配(如 Codex 的四轮降级)提高成功率,却可能在多个相似位置中命中错误的一个。Aider 的折中策略是在精确匹配失败后仅放宽前导空白,这覆盖了最常见的缩进漂移,又不会像全量规范化那样引入过多歧义。
## 不关心字节怎么来的撤销

上面每种失败都有个安静版本:编辑应用到错误位置,或覆盖在陈旧假设之上,而代理报告成功。你在 review 时发现,然后想退回那一轮。

各家有各自答案。Claude Code 的 checkpoints 在每次 prompt 前捕获代码,`/rewind` 恢复代码、对话或两者。文档也写清边界:「Checkpointing 不追踪 Bash 命令修改的文件」,多数子代理编辑也不会恢复。Aider 把每次编辑连同描述性消息提交到 git,`/undo` 丢弃最后一处改动。各自只覆盖自己代理的编辑,别的一概不管。

我本人开发 Hermes IDE,请带着这一点看待下面的内容。Hermes 在各自终端里运行 Claude Code、Codex、Aider 等代理,保存每一轮代理操作,因此可以对任意单轮 diff 或恢复,并支持撤销(见 README)。会话下方的条带列出代理回复及各自改动的行,点击即可查看改动或把文件恢复到那一刻(2.3.1 版本说明)。Review Desk(⌘G)按轮次或按文件展示同样的工作。

因为快照针对的是你的文件,而非编辑调用,同一次恢复对所有代理都有效,无论变更以 `old_string`、补丁信封还是 SEARCH 块到来。有两个限制:在带额外文件夹的 Claude Code 任务中,条带只追踪主项目(2.3.1 说明);在独立 worktree 中运行的任务,其轮次快照将在会话关闭 14 天后删除(2.2.9 说明)。

## 实践建议

如果你在含重复块的文件(如按环境划分的配置、测试数据表)上用 Codex 或 Aider,检查 diff 落点,而不只是看它说了什么。

如果 Claude Code 反复失配,先检查文件里是否混用制表符与空格、或存在不可见字符,再怪模型。如果用 Aider 的 `whole` 格式,代理工作期间别让其他写入者碰那个文件,因为基于陈旧副本的重写会无错应用。

无论哪种协议,在编辑路径之外保留一份按轮记录:普通 commit、Aider 的自动提交,或轮次快照。

编辑格式本质是结构化输出,另一端是严格解析器,Aider 的排行榜说明即便是顶级模型也有一定比例搞错格式。如果你写要求模型输出被程序解析的 prompt,结构化输出正是《Prompt Engineering Pocket Guide》涵盖的主题之一。
#Claude Code#Codex apply_patch#Aider edit formats#AI编程代理#编辑协议对比#SEARCH/REPLACE#代码补丁应用
分享:

相关推荐