1. 先搞清楚 Claude Code Checkpoint 到底在解决什么问题
Claude Code 的 Checkpoint 机制,简单说就是给对话式编程加了一个“撤销栈”。你在会话里让模型改了三个文件,发现方向不对,想回到改动之前——这时候需要的不只是文件回滚,还有对话上下文的回退。Checkpoint 就是干这个的。
它适合谁?适合需要在本地复现或调试 Checkpoint 行为的开发者,尤其是想搞清楚 JSONL transcript 怎么写入、怎么读取、rewind 怎么触发回滚的人。如果你只是日常用 Claude Code 写代码,知道/rewind能回退就够了;但如果你想在自己的工具里复刻这套机制,或者排查“为什么 rewind 没生效”,那就得往下看实现细节。
核心难点在于:如果每次对话都保存所有文件的完整内容,体积会爆炸;如果只保存对话,文件状态又恢复不了。Claude Code 的解法是两个独立系统加三层存储——文件快照系统负责文件级恢复,Transcript 系统负责对话级恢复,两者通过 messageId 关联。下面我会从配置骨架开始,一步步带你把这条链路跑通,包括 settings.json 怎么写、JSONL 长什么样、rewind 怎么验证。
2. 前置准备:TaoToken 统一 Key 与 API 通道
在复现 Checkpoint 行为之前,你需要一个能稳定调用 Claude 系列模型的通道。TaoToken 提供统一的 Key 和 API 入口,把模型调用、Coding Plan、控制台管理都收在同一个账号体系下,省去分别配置多个供应商的麻烦。
具体来说,你需要做三件事:
第一,拿到 API Key。访问控制台创建密钥,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建后复制保存,后面配置里要用。
第二,确认 API 基地址。TaoToken 的 API 入口是 https://taotoken.net/api ,注意这个地址不加 UTM 参数,直接作为 base_url 使用。
第三,如果你打算长期跑编码任务或 Agent 流程,可以了解 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它适合需要持续调用、频繁触发 Checkpoint 的场景。
注意:API Key 只显示一次,创建后立刻保存到本地环境变量或配置文件,不要硬编码在会提交到 Git 的文件里。
3. 可复制配置:settings.json 骨架与 JSONL 写入路径
Claude Code 的 Checkpoint 行为受settings.json控制。下面是一个可复制的最小配置骨架,重点开启 file history 并指定 transcript 存储位置。
{ "fileHistory": { "enabled": true, "backupRoot": "~/.claude/backups", "transcriptPath": "~/.claude/transcripts", "snapshotOnUserMessage": true, "trackBeforeEdit": true }, "api": { "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "model": "claude-sonnet-4-20250514" }, "session": { "persistTranscript": true, "transcriptFormat": "jsonl" } }几个关键字段说明:
fileHistory.enabled是总开关,关掉之后 Layer 1 和 Layer 2 都不会触发。backupRoot是物理备份的存放目录,每个文件按内容哈希分目录,版本号从 v1 递增。transcriptPath是 JSONL 文件的落盘位置,通常按会话 ID 分文件。snapshotOnUserMessage对应 Layer 2,在每条用户消息处理完后创建完整快照。trackBeforeEdit对应 Layer 1,在文件被修改前先备份。
API 部分用环境变量引用 Key,避免明文。baseUrl 指向 TaoToken 的 API 入口,这样模型调用走统一通道,Checkpoint 的触发链路和模型响应在同一会话里完成。
配置写好后,启动 Claude Code 时会话目录下会出现类似这样的结构:
~/.claude/ ├── backups/ │ └── a1b2c3d4/ │ ├── main.py@v1 │ └── main.py@v2 └── transcripts/ └── session-20250610.jsonlJSONL 文件里每一行是一条独立记录,Checkpoint 相关的记录长这样:
{"type":"file-history-snapshot","messageId":"msg-002","snapshot":{"files":{"main.py":{"version":1,"backupPath":"~/.claude/backups/a1b2c3d4/main.py@v1","timestamp":1749523200}}},"isSnapshotUpdate":true} {"type":"file-history-snapshot","messageId":"msg-003","snapshot":{"files":{"main.py":{"version":2,"backupPath":"~/.claude/backups/a1b2c3d4/main.py@v2","timestamp":1749523260}}},"isSnapshotUpdate":false}isSnapshotUpdate为 true 表示这是 Layer 1 的单文件增量备份,为 false 表示这是 Layer 2 的完整快照。读取时按 messageId 索引,rewind 就是根据 messageId 找到对应快照再回写文件。
4. 验证请求:触发 Checkpoint 并检查 JSONL 落盘
配置就绪后,用一次实际修改来验证整条链路。我试过的最小验证流程如下。
第一步,准备一个测试文件:
mkdir -p ~/checkpoint-demo && cd ~/checkpoint-demo echo "def hello():" > main.py echo " return 'v1'" >> main.py第二步,启动 Claude Code 并让它修改这个文件。在会话里输入:
把 main.py 里的返回值改成 'v2'第三步,观察备份目录和 transcript 文件的变化。修改完成后执行:
ls -la ~/.claude/backups/*/ cat ~/.claude/transcripts/session-*.jsonl | tail -5你应该能看到至少两条 file-history-snapshot 记录:一条是修改前的 Layer 1 备份(isSnapshotUpdate=true),一条是消息处理完后的 Layer 2 完整快照(isSnapshotUpdate=false)。备份目录里会出现 main.py@v1 和 main.py@v2 两个文件。
第四步,验证 rewind。在会话里执行:
/rewind msg-002其中 msg-002 是你要回退到的消息 ID,可以在 transcript 里找到。执行后检查 main.py 内容:
cat main.py如果返回的是 'v1',说明 rewind 成功从快照恢复了文件状态。同时 transcript 里 msg-002 之后的消息会被标记为已回退,对话上下文也回到对应位置。
提示:rewind 的 messageId 必须精确匹配 transcript 里的记录。如果记不住 ID,可以先
grep file-history-snapshot把快照记录列出来,再选目标。
5. 本篇常见错排查
5.1 rewind 报 Snapshot not found
最常见的原因是 messageId 写错了,或者该消息根本没有触发快照。检查 transcript 里是否存在对应 messageId 的 file-history-snapshot 记录。如果没有,说明 Layer 1 或 Layer 2 没触发,回到 settings.json 确认fileHistory.enabled和snapshotOnUserMessage是否为 true。
另一个可能是 transcript 文件被截断或轮转。JSONL 是追加写入的,如果手动清理过文件,旧记录会丢失。rewind 依赖完整的历史记录,不要随意删 transcript。
5.2 备份目录为空
如果~/.claude/backups下什么都没有,先确认文件修改是否真的发生了。Layer 1 是同步前置钩子,只有实际执行文件写入时才会触发。如果模型只是回复了文本没有调用文件编辑工具,就不会有备份。
还要检查backupRoot路径是否有写权限。在某些系统上~展开可能不符合预期,建议用绝对路径测试一次。
5.3 JSONL 里只有 isSnapshotUpdate=true 没有 false
这说明 Layer 1 触发了但 Layer 2 没触发。Layer 2 是异步后置钩子,在用户消息处理完毕后执行。如果会话被提前中断,或者snapshotOnUserMessage被设为 false,就不会有完整快照。
另外注意 Layer 2 是void调用的异步操作,不阻塞主流程。如果进程在异步任务完成前退出,记录可能来不及写入。验证时确保会话正常结束再检查文件。
5.4 文件恢复了但对话没回退
这是两个系统分离设计的正常表现。File History 只负责文件快照,Transcript 负责对话记录。rewind 时两者都会处理,但如果 Transcript 的写入失败或 messageId 对不上,文件可能恢复了而对话没动。检查 transcript 里 rewind 操作本身是否被记录,以及后续消息是否正确标记。
5.5 API 调用失败导致 Checkpoint 链路中断
如果模型请求本身失败,文件修改不会发生,Layer 1 自然不触发。确认 baseUrl 指向 https://taotoken.net/api ,Key 有效且额度充足。可以在模型对话页面先做一次简单调用验证通道,地址是 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。
6. 接入与排障入口
Checkpoint 的调试核心在于两件事:Key 通道是否通,transcript 是否完整。如果你在接入过程中遇到 API 报错或鉴权问题,先去 API Keys 页面确认密钥状态,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 base_url 配置和常见错误码说明。
如果你用的是 Claude Code 的 Anthropic 兼容模式,参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite 里的配置示例,确保请求格式和 Checkpoint 触发条件匹配。
长期跑编码任务的话,Coding Plan 能减少频繁鉴权带来的中断,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。把 Key 和通道稳定下来之后,Checkpoint 的 JSONL 记录才会连续,rewind 才有可靠的目标可查。