Codex 回退对话却不回退文件,暴露了 AI 编程助手真正的工程难题

Codex 双击 Esc 可以回退对话,却不会同步回退文件,导致模型基于错误状态继续回答。OpenAI 曾两次尝试修复,但都因触碰用户 git 仓库而引发问题。社区项目 codex-rewind 则提出不写入 git、单独维护快照的第三种方案。

不少开发者在使用 OpenAI Codex 时,可能都遇到过类似场景:AI 修改了多个文件后发现方向不对,于是双击 Esc 回退几轮对话。但表面上对话回到了过去,磁盘上的文件却仍停留在被修改后的状态。

更麻烦的是,模型之后还会基于这些并未回滚的文件继续分析,形成一种“上下文已回退、文件系统未回退”的错位。这个问题并不是用户误操作,而是 Codex 在对话回滚与文件状态管理之间长期存在的一致性缺口。相关讨论已在 GitHub issue 中挂了很久,例如 #22100 明确指出了这一现象,#9203 要求恢复 /undo,#11626 则希望对话和代码能够一起回滚。

OpenAI 两次尝试,都没有真正解决

更值得注意的是,OpenAI 并非没有尝试修复,而是已经做了两次,但都没有形成稳定方案。

第一次方案名为 Feature::GhostCommit,配置键叫 undo。思路很直接:在用户的 git 仓库中写入游离 commit,回退时 checkout 到先前状态。听起来合理,但在 #8214 中却引发了数据丢失问题,恢复路径影响了用户 index,部分场景下文件被直接覆盖。随后,这一功能在 PR #8424 中被整体下线。

这次失败并不只是工程疏忽。为了安全,该方案每次快照都需要运行 git status –untracked-files=all,并使用一次性临时 index,以避免触碰用户自己的 index。但代价是 git 的 stat cache 失效,每次快照都要重新哈希所有改动过的文件。为了弥补性能损失,方案又不得不加入异步执行、240 秒 watchdog,以及大量硬编码排除规则,例如超过 10MB 跳过、超过 200 条目目录跳过、node_modules 跳过等。安全设计反而带来了更复杂的工程负担。

第二次尝试则转向 Codex Desktop,继续在用户仓库中保存检查点,具体位置是 refs/codex/turn-diffs/ 下的 git 对象。这一次没有直接造成数据丢失,却带来了另一个问题:在 #29388 中,一个 5.7GB 的项目里出现了 102GB 的孤儿对象,且没有 GC。同时,这些非标准 refs 还导致基于 libgit2 的第三方客户端出现兼容性问题,对应 #28241。

两次方案,一个影响数据安全,一个拖累磁盘和兼容性。它们共同说明了一点:把 AI 助手自己的状态检查点写进用户 git 仓库,本身就是一个高风险选择。

社区给出第三种思路:不碰 git,单独做快照

围绕这一问题,社区项目 codex-rewind 提出了一种不同的做法。它不再向用户仓库写入任何 git 对象,而是在 Codex 自己的目录中维护文件快照。

其结构大致分为四层:

  • CODEX_HOME/file_snapshots/blobs/<xx>/<hash>:按内容寻址保存文件内容,天然支持去重。
  • manifests/<manifest-id>:每个检查点保存一份清单,包含 path、mode、size、mtime、hash 等信息。
  • refs/<thread-id>:记录 thread 对应的 turn_id 与 manifest-id 列表。
  • turns/<turn-id>:记录每个 turn 对应的 manifest-id。

这种设计绕开了此前两次方案的主要问题:不写入 git 仓库,就不会产生孤儿对象;不触碰 .git 和用户 index,也就不会破坏现有工作流;检查点存放在 Codex 自己的目录中,第三方 git 客户端也不会误读。

不过,不依赖 git 也意味着没有现成的垃圾回收机制。git gc 不会管理 CODEX_HOME 下的内容,因此 codex-rewind 需要自己实现 GC,通过引用计数和 mark-and-sweep 等方式,将快照生命周期绑定到会话上。

使用方式相对简单,安装后通过 npm i -g codex-rewind 获取工具,并用 codexr –enable file_snapshots 启用。codexr 与原版 codex 命令并存,开启文件快照后,新会话中的文件变化会随每个 turn 自动记录。回退时输入 /rewind,可以选择回到此前某条提示词对应的对话与文件状态;若要恢复,则使用 /redo。

需要注意的是,该功能是会话级开关,而不是每次调用级的临时开关。新会话开启后可以持续跟踪,但已经在运行中的旧会话无法中途补录前面的快照。

为什么这个方案更可行:它限制了跟踪范围

codex-rewind 的关键并不只是“不用 git”,还在于它没有遍历整棵目录树。它通过三个“有界分区”的并集确定跟踪范围,从而避免把大量构建产物纳入快照。

素材中提到,在同一个 checkout 上,完整子树遍历涉及 70,609 个文件 / 116GB,其中几乎全是构建产物;而三个分区的并集只有约 6,096 个文件 / 59MB。文件数约为完整遍历的 1/11.6,体积约为 1/1975。

时间维度上的差异更明显。同一个仓库隔几个小时后再测,子树遍历从 58,116 个文件增加到 70,609 个文件,增幅达到 21%,主要来自几个小时的编译产物;而 git 分区两次都稳定在 5,996 个文件。这说明,如果跟踪范围随着仓库被工作时间不断增长,性能预算将很难控制,也解释了第一次方案为何即使加入排除列表仍然难以支撑。

当然,这一方案也不是全覆盖。作者自己也承认,一个已经脱离 git 跟踪、之后只被 shell 命令改动、又掉出“最近 100 个”窗口的文件,可能不会被跟踪。这个交集很窄,但确实存在。

这不是一个 Codex 专属问题

有人可能会问:既然在 git 仓库里,为什么不直接用 git checkout?对于工作区干净、开发者熟悉 git 的场景,这确实够用。但越来越多用户使用 Codex 处理的不只是代码,还包括文案、表格、研究笔记、合同草稿等内容。在这些目录里,git init 并不是自然工作流。

即使是在 git 仓库中,依赖手动提交也并不轻松。素材中提到,有用户在 issue 中表示,从 Claude Code 切换到 Codex 后,每轮对话后都要向 Git 提交一次,非常麻烦。使用 stash 也会遇到自身的问题;让模型主动记住打检查点,则依赖上下文窗口做簿记,可靠性有限;让 Agent 自己撤销,也很难精确重建 shell 命令、MCP 工具调用造成的文件变化。

其他 AI 编程工具同样在探索类似能力。Claude Code 有 checkpoint,但只跟踪内置编辑工具的改动,shell 命令修改的文件不在恢复范围;OpenCode 则在内部维护 git 仓库做快照,官方文档也承认大仓库会变慢并占用磁盘。目前行业还没有一个完美方案,但 codex-rewind 至少选择了一条与 OpenAI 此前两次失败方案不同的路径:不把检查点塞进用户仓库。

从更长的时间线看,这个问题指向了 AI 编程助手普遍面临的一项挑战:当 Agent 越来越自主,不只是生成代码,还会执行 shell、调用 API、修改配置文件时,用户需要的“撤销”能力正在变得越来越复杂。对话回退只是表层,真正困难的是如何管理工具状态、文件系统状态和上下文状态之间的一致性。

原创文章,作者:点点,如若转载,请注明出处:https://www.dian8dian.com/codex-hui-tui-dui-hua-que-bu-hui-tui-wen-jian-bao-lu-le-ai

Like (0)
点点的头像点点
Previous 3小时前
Next 1小时前

相关推荐