Codex回退对话却不回退文件:OpenAI做了两次都没搞定的坑,有人用第三种思路补上了

上周我让Codex改一个API接口的参数校验逻辑,它一顿操作改了好几个文件,改完我发现方向完全跑偏了。双击Esc回退三轮对话,上下文确实回去了。然后我习惯性地打开编辑器想看看"之前的代码长什么样",发现文件根本没动,还是被改过之后的状态。 当时我愣了一下,心想:这不对吧? 然后更离谱的事来了。我接着在回退后的对话里问它"你刚才那个参数校验改完之后,类型定义还一致吗",它居然煞有介事地分析了一遍。问题是它分析的是被覆盖后的文件,而不是回退前的。它拿着新文件假装在看旧代码,我在旁边看得直冒冷汗。 这不是个小bug。模型拿到的是回退后的旧对话,看到的却是磁盘上的最新文件,两边对不上,它就在一本正经地胡说八道。这个问题在Codex的issue tracker上挂了大半年,#22100里写得明明白白。#9203要求把 /undo 加回来,#11626要求"对话和代码一起回滚",截至本月两个issue都还开着。

官方做过两次,两次都死在同一个地方

这事最让我意外的不是"没做",而是"做了两次都没做成"。

第一次用的是 Feature::GhostCommit,配置键叫 undo。思路很直觉:在用户的git仓库里写游离commit,回退时checkout到之前的状态。听着没毛病对吧?结果在 #8214 里直接造成了数据丢失------恢复路径动了用户的index,有些场景下文件直接被覆盖了。修复之后两天,整个功能在 PR #8424 里被整体下线。 我第一次看到这段历史的时候,觉得大概是"工程师不够小心"。但仔细看完技术分析,发现不是这么回事。 问题的根子在于:为了安全,每次快照要跑一次 git status --untracked-files=all,外加一个一次性的临时index。临时index是故意的安全设计------为了不碰用户自己的index。但代价是git的stat cache完全失效,每次快照都要从头哈希所有改动过的文件。 git status 之所以快,靠的就是stat cache。为了安全丢掉它,快照就慢下来了;慢下来之后,被迫加异步执行、加一个240秒的watchdog、加一堆硬编码排除列表(超10MB跳过、超200条目目录跳过、node_modules跳过......)。一条很清楚的因果链:为了安全避开用户的index → 失去stat cache → 慢 → 复杂度一层层堆上去

第二次尝试换了个姿势,但犯了同一个错误------还是往用户的仓库里写东西。Codex Desktop现在还在用户的仓库里、refs/codex/turn-diffs/ 下面保存整棵树的git对象检查点。这次没造成数据丢失,但造成了另一个问题:一个5.7GB的项目里长出了102GB的孤儿对象(#29388),没有GC。而且这些非标准refs还让基于libgit2的第三方客户端出了兼容性故障(#28241)。

两次实现,一个丢数据,一个撑爆磁盘。 看到这儿我其实有点哭笑不得。两拨人都很聪明,都做了安全设计,结果安全设计本身成了问题的根源。这就像你为了防贼在门口装了十道锁,结果每次回家自己也得花半小时开门------最后你不是被贼烦死的,是被自己的安全措施折腾死的。 结论其实就一句话:用户的仓库不是Codex该往里写东西的地方。

那怎么办?有人换了个思路

知道这个问题挂了大半年没解决,我中间一度认命了------自己每轮手动commit呗,还能咋地。直到最近在GitHub上翻到一个叫 codex-rewind 的项目,仔细看了看它的设计,觉得"嗯,这个思路确实不一样"。

python 复制代码
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仓库写任何东西。没有index被破坏------因为根本不碰.git。没有被第三方客户端误读------因为那些检查点待在Codex自己的目录里,git工具链根本看不见。 但它引入了一个新问题:磁盘占用没有现成工具帮你回收。git gc不会管 CODEX_HOME 下面的东西。所以这个子系统必须自己实现GC------引用计数加mark-and-sweep,生命周期绑定到会话上。 安装和使用倒是简单:

css 复制代码
npm i -g codex-rewind
codexr --enable file_snapshots

codexr 和原版 codex 命令并存,不替换。开启文件快照后开始新会话,文件快照在后台随每个turn自动记录。回退的时候输入 /rewind,选一条之前的提示词,对话和文件一起回去。改主意了输入 /redo 走回来。 不过有个限制得提前知道:这个开关是会话级的,不是每次调用级的。新会话开了就跟踪,已经跑到一半的旧会话中途开不了------不能补录前面的快照。

跟踪哪些文件------这个设计值得细看

不遍历整棵目录树,这是codex-rewind和前两次尝试最本质的区别。它用三个"有界分区"的并集来确定跟踪范围:

分区 来源 边界 在Codex仓库上的实测
git-tracked 直接读 .git/index(用gix库,不fork git命令) 项目边界 5,996文件 / 56.4MB / 265ms
edit-touched 本次会话中Agent工具实际写过的路径 会话动作边界 与改动量成正比
recently modified 前两者之外,按mtime排序的补充 硬上限:最多100个额外文件,单文件16MB 100文件 / 2.4MB / 160ms

关键在"有界"两个字。同一个checkout上,完整子树遍历是 70,609个文件 / 116GB------几乎全是构建产物。三个分区的并集约 6,096个文件 / 59MB。文件数是1/11.6,体积是1/1975。 更有意思的是时间维度的对比:同一个仓库隔几个小时再测,子树遍历从58,116文件涨到70,609文件,多了21%,纯粹来自这几个小时的编译。而git分区一动没动,两次都是5,996个文件。 一个随"仓库被工作了多久"增长的开销,是没法做预算的。这也解释了为什么第一次尝试加了一堆硬编码排除列表还是扛不住------排除列表追不上构建目录的膨胀速度。 当然,覆盖率不是100%,这个作者自己也直说了:一个已经脱离git跟踪、之后只被shell命令改动、又掉出了"最近100个"窗口的文件,不被跟踪。交集很窄,但确实存在。

为什么不直接用git

"在git仓库里工作,git checkout 不就行了?"------这个反驳在每个相关讨论串里都会出现。 如果你在git仓库里、工作区干净、开着Agent的人是开发者,那确实够用。但问题是:越来越多人拿Codex干的活不是写代码。文案、表格、研究笔记、合同草稿------在这些目录里 git init 不是工作流,是一件戏服。 而且就算在git仓库里,"用git就行"这个方案落到最后也是手工活。有人在issue里吐槽得很直接:

Since switching from Claude Code to Codex, I have to commit to Git after every single turn in the conversation, which is a huge hassle.

每轮提交一次,烦不烦?用stash的话,stash自己有它的坑。让模型记得打检查点呢------这是在用上下文窗口来做簿记,和任何"需要模型记住"的事情一样不可靠。让Agent自己撤销呢------shell命令、MCP工具调用改文件不会留下结构化的变更记录,Agent没法从一段对话记录里精确重建"我改了什么"。

而最硬的证据是:OpenAI做了两次。如果 git checkout 就能覆盖,第一次就不会上线又被拉下来,第二次也不会至今还留在Desktop里以另一种方式出问题。

顺带说一句,其他工具也没完全搞定

Claude Code有checkpoint,但只跟踪内置编辑工具的改动,shell命令改的文件不在恢复范围。OpenCode在内部维护git仓库做快照,官方文档自己都承认大仓库会慢会占磁盘。这问题各家都有自己的取舍,目前没有完美方案。codex-rewind至少选了一条被证明走不通的反方向:不碰git。

写在最后

这个问题本质上是AI编程工具普遍还没想清楚的一件事:Agent改了文件之后,"后悔药"到底该怎么做? 对话回退只是表象。真正的问题是,当Agent越来越自主,不只是写代码,还会跑shell、调API、改配置文件,我们给它的"撤销"能力远远跟不上它"搞事"的速度。Codex的两次翻车、codex-rewind的第三种尝试,本质上都是在回答同一个问题:AI工具的状态管理,到底该由谁来管、管多深。

我装上codex-rewind用了几天,日常写代码的场景基本够用了。不过我有个习惯是每个任务开新会话,所以"中途不能开关"这个限制对我影响不大。如果你的工作流是长时间在一个会话里反复调整,可能会碰到覆盖盲区。 准备再观察一段时间,稳定了的话就把它写进团队的标准工作流里。项目地址:github.com/extracurric... ,RFC写得比这篇文章详细得多,想深挖的可以直接看。

相关推荐
kyriewen1 小时前
我写了1个复盘Skill,和AI协作踩过的坑第二天自动变成护栏
前端·程序员·ai编程
plainGeekDev3 小时前
Agent的两种玩法:parallel 与 pipeline
agent·ai编程·claude
该用户已不存在3 小时前
8 个 MCP Server 让我的 Claude Code 升级成高级开发者
ai编程·mcp
杨杨杨大侠3 小时前
MCP、ToolFunction 与 Skill:从模型输入到工具执行
后端·aigc·openai
plainGeekDev4 小时前
Workflow 基础:用代码调度多个 Agent
agent·ai编程·claude
0end15 小时前
AI Agent 学习笔记(七):Coding Agent 与代码生成——代码是 Agent 的元能力
ai编程
Lambert2815 小时前
Spring AI 2.0 vs LangChain4j 1.19:MCP 新规范竞速,Spring AI 慢在哪
langchain·ai编程
Sword995 小时前
近 90 天用了 14 亿 Token,我拿 AI 干了些什么
ai编程
、如果6 小时前
推文评论数据怎么拿?X(Twitter)评论分析 Skill 的工程拆解与实测
爬虫·ai编程·twitter