Rewind 机制调研

Codex CLI vs Claude Code — 如何回退工作区的磁盘修改

调研日期:2026-06-25

结论速览

Codex CLI

Rewind 只回退对话历史,磁盘文件完全不管。协议层明确声明"Clients are responsible for undoing any edits on disk",客户端通常借助 git 自行处理。

Claude Code

自建了一套文件快照系统,编辑前备份、响应后打快照、rewind 时从备份覆盖还原。完全不依赖 git,在非 git 项目中也能工作。

对比总览

维度 Codex CLI Claude Code
磁盘回退 不支持 交给客户端 自己实现 文件快照系统
依赖 git 客户端通常用 git 回退 完全不依赖
备份存储 ~/.claude/file-history/{sessionId}/
对话历史回退 追加 RollbackEvent 标记,重建历史 截断到目标 messageId
变更检测 stat → mtime → 内容 diff 三级优化
跨 session 支持 通过 hard-link 继承旧 session 备份
快照上限 无(仅对话) 最多 100 个,FIFO 淘汰
用户选择粒度 只能回退 N 个 turn 可选:代码+对话 / 仅对话 / 仅代码
C

Codex CLI — ThreadRollback

设计哲学

Codex 的 rollback 只操作对话状态。协议层写明:

"This does not attempt to revert local filesystem changes. Clients are responsible for undoing any edits on disk."

执行流程

1

请求验证

检查 numTurns ≥ 1,确认无正在执行的 turn,防止并发

2

持久化标记

向 rollout 文件追加 ThreadRolledBackEvent(不删除历史,保留审计记录)

3

历史重建

反向扫描 rollout 流,跳过被标记的 turn,再正向重放剩余 items

4

裁剪内存

drop_last_n_user_turns() 找到第 N 个 user turn 边界,截断之后内容

关键文件

核心处理 codex-rs/core/src/session/handlers.rs :451-549

历史重建 codex-rs/core/src/session/rollout_reconstruction.rs

内存裁剪 codex-rs/core/src/context_manager/history.rs :224-247

请求入口 codex-rs/app-server/src/request_processors/thread_processor.rs :1679-1737

协议定义 codex-rs/protocol/src/protocol.rs :639-643

C

Claude Code — File History Checkpoint

设计哲学

Claude Code 自建了一套与 git 无关的文件版本管理系统,在编辑发生前主动备份,rewind 时直接从备份覆盖还原。核心实现在 src/utils/fileHistory.ts(约 1100 行)。

三阶段工作流

阶段 1 编辑前备份 — fileHistoryTrackEdit()

当 FileEditTool / FileWriteTool / BashTool / NotebookEditTool 要修改文件时,在编辑发生前被调用。

  • 检查文件是否已在当前快照中被追踪
  • 若未追踪,将文件当前内容复制为 v1 备份
  • 备份文件名:{sha256(filePath).slice(0,16)}@v1
阶段 2 响应结束时打快照 — fileHistoryMakeSnapshot()

每轮对话结束后自动调用,关联到当前 messageId。

  • 遍历所有被追踪的文件
  • 对比当前文件与最新备份(stat → mtime → 内容 diff)
  • 有变化则创建新版本(v1 → v2 → v3...)
  • 生成 FileHistorySnapshot,最多保留 100 个
阶段 3 Rewind 还原 — fileHistoryRewind()

用户触发 rewind,选择目标消息后执行。

  • 根据 messageId 找到对应的 snapshot
  • 遍历所有被追踪的文件:
  • 备份为 null(目标时刻不存在)→ 删除文件
  • 有备份 → 用 copyFile() 覆盖还原 + 恢复权限

核心数据结构

FileHistoryBackup {
  backupFileName: string | null  // null = 文件在此版本不存在
  version: number                // 递增:v1, v2, v3...
  backupTime: Date
}

FileHistorySnapshot {
  messageId: UUID                              // 关联到对话消息
  trackedFileBackups: Record<path, Backup>      // 文件路径 → 备份
  timestamp: Date
}

FileHistoryState {
  snapshots: Snapshot[]          // 最多 100 个
  trackedFiles: Set<string>      // 当前追踪的文件
  snapshotSequence: number       // 递增计数器
}

备份存储

~/.claude/file-history/
└── {sessionId}/
    ├── a1b2c3d4e5f6g7h8@v1    # 文件 A 的第 1 个版本
    ├── a1b2c3d4e5f6g7h8@v2    # 文件 A 的第 2 个版本
    └── 9f8e7d6c5b4a3210@v1    # 文件 B 的第 1 个版本

用户可选的 Rewind 模式

代码 + 对话

文件回退 + 对话截断

仅对话

文件不动,只截断对话

仅代码

只回退文件,对话保留

性能优化

copyFile() 而非 readFile+writeFile,避免大文件撑爆内存
三级变更检测:stat → mtime → 内容 diff
跳过未变文件:若文件未变,复用上一版本备份
跨 session 继承:通过 hard-link 避免重复拷贝

关键文件

核心实现 src/utils/fileHistory.ts

UI 组件 src/components/MessageSelector.tsx

命令入口 src/commands/rewind/rewind.ts

集成点  src/tools/FileEditTool/FileEditTool.ts :435

集成点  src/tools/FileWriteTool/FileWriteTool.ts :259

集成点  src/tools/BashTool/BashTool.tsx :393

架构对比图

Codex CLI

用户触发 Rewind

指定回退 N 个 turn

追加 ThreadRolledBackEvent

rollout 文件只追加不删除

反向扫描 + 正向重放

重建对话历史

drop_last_n_user_turns()

裁剪内存中的对话

磁盘文件?不管。

客户端自己用 git 处理

Claude Code

编辑前

fileHistoryTrackEdit() 备份 v1

响应结束

fileHistoryMakeSnapshot() 打快照

用户触发 Rewind

选择目标消息 + 恢复模式

查找目标 Snapshot

按 messageId 定位

遍历文件,从备份覆盖还原

null → 删除 / 有备份 → copyFile 还原

设计取舍分析

Codex 为什么不管磁盘?

  • 审计优先:rollout 文件保留完整历史(包括被回退的部分),方便审计
  • 沙盒边界:文件修改通过 tool call 完成,Codex 无法可靠地逆向任意操作(比如 bash 里的 rm、mv)
  • 职责分离:Codex 是协议层,客户端(如 VS Code 扩展)负责 UI 和文件管理

Claude Code 为什么自建文件快照?

  • 独立运行:Claude Code 是终端应用,没有外部客户端可以委托
  • 不依赖 git:在非 git 项目中也能工作
  • 用户体验:提供三种粒度的恢复模式,比纯 git reset 灵活
  • 追踪粒度:只追踪 Claude 修改过的文件,不是整个工作区的快照

Claude Code 方案的局限

  • Bash 命令的间接修改:如果 Claude 通过 bash 执行了 rm -rf 或复杂脚本,只有被显式追踪的文件能被还原
  • 存储开销:每个版本都是完整副本,不是 diff,大文件会占用较多空间
  • 100 快照上限:超长 session 的早期快照会被淘汰