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 | 可选:代码+对话 / 仅对话 / 仅代码 |
Codex 的 rollback 只操作对话状态。协议层写明:
"This does not attempt to revert local filesystem changes. Clients are responsible for undoing any edits on disk."
请求验证
检查 numTurns ≥ 1,确认无正在执行的 turn,防止并发
持久化标记
向 rollout 文件追加 ThreadRolledBackEvent(不删除历史,保留审计记录)
历史重建
反向扫描 rollout 流,跳过被标记的 turn,再正向重放剩余 items
裁剪内存
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
Claude Code 自建了一套与 git 无关的文件版本管理系统,在编辑发生前主动备份,rewind 时直接从备份覆盖还原。核心实现在 src/utils/fileHistory.ts(约 1100 行)。
当 FileEditTool / FileWriteTool / BashTool / NotebookEditTool 要修改文件时,在编辑发生前被调用。
{sha256(filePath).slice(0,16)}@v1每轮对话结束后自动调用,关联到当前 messageId。
FileHistorySnapshot,最多保留 100 个用户触发 rewind,选择目标消息后执行。
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 个版本
代码 + 对话
文件回退 + 对话截断
仅对话
文件不动,只截断对话
仅代码
只回退文件,对话保留
核心实现 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
用户触发 Rewind
指定回退 N 个 turn
追加 ThreadRolledBackEvent
rollout 文件只追加不删除
反向扫描 + 正向重放
重建对话历史
drop_last_n_user_turns()
裁剪内存中的对话
磁盘文件?不管。
客户端自己用 git 处理
编辑前
fileHistoryTrackEdit() 备份 v1
响应结束
fileHistoryMakeSnapshot() 打快照
用户触发 Rewind
选择目标消息 + 恢复模式
查找目标 Snapshot
按 messageId 定位
遍历文件,从备份覆盖还原
null → 删除 / 有备份 → copyFile 还原
rm -rf 或复杂脚本,只有被显式追踪的文件能被还原