news 2026/10/3 6:50:50

Claude Code 是怎么恢复一段会话的?从 JSONL 到 parentUuid 的完整链路拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Claude Code 是怎么恢复一段会话的?从 JSONL 到 parentUuid 的完整链路拆解

1. 会话恢复到底在恢复什么:从 claude --continue 说起

很多人第一次用 Claude Code 的会话恢复,都会有一个直觉:这不就是把聊天记录重新读一遍吗?我一开始也这么想,直到有次在 fix-login 分支上做到一半关掉终端,第二天claude --continue回来,发现它不光把旧消息贴回来了,连之前用的代码审查 agent、worktree 目录、甚至读过哪些文件都还在。这时候才意识到,恢复的其实是一整套“可继续操作的工作现场”,而不只是几行文字。

先把概念说清楚。Claude Code 是 Anthropic 出的命令行编码助手,它把每段会话按项目落盘成 JSONL 文件,默认路径是~/.claude/projects/<项目目录>/<session-id>.jsonl。所谓会话恢复,就是从这个文件里把对话消息、工具调用结果、以及 agent/worktree/文件历史这些状态记录重新读出来,拼成一个能接着用的进程。适合谁?适合所有在终端里用 Claude Code 做长期任务的人——尤其是那种一个需求要跨天、跨终端、跨分支的场景。

三个入口要分清:

入口用途
claude --continue继续当前项目最近的一次会话
claude --resume <id 或名称>返回指定会话;不带参数时打开选择器
/resume在当前 Claude Code 进程里切换会话

注意“最近”是按项目算的,不是全局。你在 A 项目里刚聊完,切到 B 项目跑--continue,恢复的是 B 项目自己的最近会话。--continue和--resume是启动时恢复,/resume是运行中切换,入口不同,但后面读日志、重建消息链的步骤基本一致。

真正的恢复分两步走:第一步,Claude Code 从本地 JSONL 重建一段可以继续操作的会话;第二步,等你输入新消息后,它才整理下一次发给模型的上下文。这两步经常被混为一谈,但排查问题时必须分开看——终端里显示的历史,和模型实际收到的上下文,不一定完全相同。

理解这一点,后面看 JSONL 结构和 parentUuid 链才不会绕晕。你可以先记住一句话:磁盘上留下的是“发生过什么”,恢复出来的是“从哪继续”,发给模型的是“这次要带什么”。

2. JSONL 会话文件与 parentUuid 父子链的读取逻辑

JSONL 全称 JSON Lines,按行存 JSON,每行一条独立记录。这个格式的好处是程序可以边运行边往文件末尾追加,不用每次重写整个会话。Claude Code 就是靠这个特性,让会话在运行中持续落盘,中途退出也不影响前面已保存的记录。

每行最外层的type是记录类型,决定 Claude Code 怎么读它。这里有个常见误解:type: "user"不代表“人说的话”,type: "assistant"也不代表“AI 的文字回答”。对于 user 和 assistant 记录,内部还有message.role,它表达的是模型交互方向——user 是送入模型的一侧,assistant 是模型返回的一侧。具体装了什么,要看message.content。所以看到顶层type: "user"时,内部是text可能是用户消息,内部是tool_result就是工具返回给模型的结果。

一次工具调用通常形成四条记录:用户提问、模型发起tool_use、工具以tool_result返回、模型继续回答。界面上看着像模型连续完成了调用和结论,但 JSONL 里两条 assistant 记录之间还夹着一条保存工具结果的 user 记录。

连接这些记录靠两个字段:

字段用途
uuid标识当前消息
parentUuid指向逻辑上的上一条消息,不一定是 JSONL 中紧邻的上一行

除了 user 和 assistant,还有一批控制与辅助记录,它们不作为普通对话显示,主要用于恢复控制状态:

type恢复时的作用
system按 subtype 恢复会话控制状态
summary帮助识别会话,不作为普通对话恢复
custom-title恢复会话列表标题
tag用于筛选和查找会话
agent-name恢复会话列表和终端中的名称
agent-setting查找并重新应用对应 agent 配置
mode恢复运行方式
worktree-stateworktree 仍存在时重新进入原工作目录
file-history-snapshot重建文件历史,供 rewind 恢复文件
pr-link恢复会话与 PR 的关联

正常对话通常只有一条路线,分叉出现在你用/rewind回到较早消息再重新输入时。因为日志是追加写入,rewind 只改变“接下来从哪继续”,不删除已发生的对话。新消息追加到文件末尾,再通过parentUuid指向较早的消息,新旧两条路线就同时留在 JSONL 里了。

看个精简例子,省略了会话 ID 和时间戳:

{"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":[{"type":"text","text":"修复登录失败"}]}} {"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"我先检查鉴权流程"}]}} {"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":[{"type":"text","text":"改用 OAuth"}]}} {"type":"assistant","uuid":"a2","parentUuid":"u2","message":{"role":"assistant","content":[{"type":"text","text":"好,我改成 OAuth"}]}} {"type":"user","uuid":"u3","parentUuid":"a1","message":{"role":"user","content":[{"type":"text","text":"先不改方案,只修密码登录"}]}} {"type":"assistant","uuid":"a3","parentUuid":"u3","message":{"role":"assistant","content":[{"type":"text","text":"问题出在密码校验逻辑"}]}}

前四行是一条完整对话,第五行虽然写在文件末尾,parentUuid仍是a1。这 6 行实际形成两条路线。假设从 a3 继续,Claude Code 沿parentUuid向前查:a3 → u3 → a1 → u1,再反转顺序,重建出 4 条按阅读顺序排列的消息。u2 和 a2 属于 OAuth 路线,不在当前链中,会被跳过但仍留在文件里。

如果对话之前被压缩过,情况会多一层。压缩发生在会话运行期间,不是恢复时。压缩后文件末尾会增加一条压缩边界 B 和一条摘要消息 S:

{"type":"system","subtype":"compact_boundary","uuid":"B","parentUuid":null,"logicalParentUuid":"a2","compactMetadata":{"trigger":"auto","preTokens":120000}} {"type":"assistant","uuid":"S","parentUuid":"B","isCompactSummary":true,"message":{"role":"assistant","content":[{"type":"text","text":"早期对话摘要……"}]}}

几个字段要记牢:subtype: compact_boundary是压缩分界点;parentUuid: null表示新链从这里开始,不再沿父引用读更早的原始消息;logicalParentUuid: a2记录压缩前处理到 a2,但重建时不沿它向前查;isCompactSummary: true表示 S 是摘要,不是用户新输入。恢复时从 a3 向前查只会得到 B → S → u3 → a3,压缩边界是内部标记,不会作为普通聊天内容发给模型。

3. 可复制配置:会话文件结构与恢复命令验证

这一节给你能直接抄的东西。先看会话文件在磁盘上的样子,再看恢复命令怎么跑,最后看状态记录长什么样。

会话文件路径规则:

# 默认位置 ~/.claude/projects/<项目目录>/<session-id>.jsonl # 实际例子(项目目录会把路径里的 / 替换成 -) ls ~/.claude/projects/-Users-me-code-fix-login/ # 输出类似: # 3f2a9c1e-....jsonl # 8b7d4e02-....jsonl

查看某段会话的原始记录:

# 看前 5 行,确认 type 和 parentUuid 结构 head -n 5 ~/.claude/projects/-Users-me-code-fix-login/3f2a9c1e-....jsonl # 只看消息链相关字段,过滤掉元数据 cat ~/.claude/projects/-Users-me-code-fix-login/3f2a9c1e-....jsonl \ | jq -c 'select(.type=="user" or .type=="assistant") | {type,uuid,parentUuid}'

恢复命令验证:

# 入口一:继续当前项目最近会话 claude --continue # 入口二:指定 session-id 恢复 claude --resume 3f2a9c1e-.... # 入口三:不带参数,打开选择器 claude --resume # 入口四:运行中切换 # 在 Claude Code 交互界面里直接输入 /resume

worktree 状态记录长这样,恢复时 Claude Code 会检查目录是否存在,存在就切过去:

{"type":"worktree-state","uuid":"w1","sessionId":"3f2a9c1e-....","worktree":{"name":"fix-login","path":"/Users/me/code/fix-login","branch":"fix/login"}}

agent 设置记录只保存名称或类型,恢复时去当前 settings 里找对应配置再应用:

{"type":"agent-setting","uuid":"g1","sessionId":"3f2a9c1e-....","agent":{"name":"code-reviewer"}}

如果你用的是第三方兼容接入方式,把 Claude Code 指向自定义端点,配置通常写在 settings 里。以 TaoToken 为例,Base URL 用https://taotoken.net/api,Key 在控制台生成,Model ID 按你选的填。三件套缺一不可:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }

这段 JSON 放在~/.claude/settings.json里,路径和字段名保持原样。改完重启 Claude Code 生效。注意 Base URL 和 Key 是两回事,Key 要去控制台单独生成,别把两者混在一个字段里。

恢复后想确认消息链重建对不对,可以对比终端显示的历史和 JSONL 里的当前链。终端里少了一段,就回头查会话选择、日志读取和消息链;终端记录还在但模型像忘了,再看上下文裁剪和压缩。这两层分开排查,不容易绕错方向。

4. 验证请求与成功结果:恢复后怎么确认真的接上了

配置改完、命令跑完,怎么知道恢复真的成功了?别只看终端有没有报错,要验证三件事:会话选对了、消息链重建对了、工作状态回来了。

第一步,确认会话选对。跑claude --resume不带参数,选择器会列出当前项目的会话,带标题、标签、agent 名称。如果你之前设过custom-title,这里能看到。选错会话是最常见的“恢复后像失忆”的原因——你以为接的是 fix-login,其实接的是另一个 worktree 的会话。

第二步,确认消息链。恢复后终端会显示当前路线上的对话。你可以翻到最早那条,看它是不是你预期的起点。如果之前 rewind 过,终端显示的应该是新路线,旧路线不在里面。想核对,用前面那条 jq 命令把当前链的 uuid 和 parentUuid 打出来,手动走一遍回溯。

第三步,确认工作状态。恢复后看几个信号:

# 当前工作目录是不是回到了原 worktree pwd # 期望输出:/Users/me/code/fix-login # 当前分支对不对 git branch --show-current # 期望输出:fix/login

如果 agent 之前用的是代码审查类型,恢复后它的行为应该和之前一致——工具范围、提示词风格都对得上。文件读取记录也会重建,你之前读过的src/auth.ts再让 Claude 编辑时,它不会重新读一遍才敢改。

一个完整的成功恢复长这样:终端贴回旧消息,pwd回到 fix-login,git branch显示 fix/login,agent 名称在选择器和终端里都对,然后你输入新消息,Claude 接着上次的进度往下做。这时候模型请求才真正开始准备,用的是“最近一次压缩位置之后的历史 + 你刚输入的新消息”。

有个细节要注意:恢复不会自动重跑工具。上次工具执行到一半终端被关,恢复时 Claude Code 会清理未完成的工具调用,而不是重新执行它。所以别指望恢复后那个跑了一半的测试自己跑完,你得手动再触发一次。

还有,恢复是有条件的。Claude Code 会重新读当前 settings,命令行参数可以覆盖会话里保存的设置。如果原 agent 被删了,或者 worktree 目录不存在了,它会退回当前可用的配置和目录,不会硬报错。这时候终端可能看着恢复了,但工作目录没切过去,得自己留意。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

恢复会话时踩的坑,大多集中在接入配置和会话选择两块。下面按真实报错对照排查。

401 Unauthorized。这个最常见,基本是 Key 的问题。检查ANTHROPIC_API_KEY是不是填对了,有没有多余空格,Key 有没有过期或被撤销。如果你用的是自定义端点,确认ANTHROPIC_BASE_URL和 Key 是配套的——A 平台的 Key 配 B 平台的 URL,必然 401。改完 settings.json 记得重启 Claude Code,环境变量不会热加载。

local proxy failed。这个报错通常出现在你配了本地转发但转发进程没起来,或者端口被占。先确认转发服务在跑,再确认 settings 里的地址和端口对得上。如果你没打算用本地转发,就把相关配置删干净,别留半截。这类问题排查顺序是:进程在不在 → 端口通不通 → 配置地址对不对。

reading choices 相关报错。这通常和模型返回格式有关,出现在接入端点返回的结构和 Claude Code 预期不一致时。检查你的 Model ID 是不是写对了,有些端点对模型名大小写敏感。另外确认返回的是标准对话结构,不是别的格式。如果换回官方端点就正常,那问题在接入端,不在 Claude Code 本身。

OAuth 相关报错。Claude Code 某些登录方式走 OAuth 流程,如果你混用了 OAuth 登录和 API Key 配置,可能冲突。二选一:要么走 OAuth 登录,要么用 Key 接入,别同时配。报错里出现 token 刷新失败、授权过期,先清掉旧的凭证再重新走一遍。

恢复后像失忆。这个不算报错,但最让人抓狂。分两层查:终端里历史就少了一段,说明会话选错或消息链没重建对,回去查--resume选的 session-id;终端历史都在但模型没接住细节,说明那段内容没进这次请求,或者已经被压缩成摘要了。记录没丢,只是没完整进入上下文。

worktree 没切过去。恢复后pwd还在老目录。检查 JSONL 里的worktree-state路径,确认那个目录还在磁盘上。目录被删了或移走了,Claude Code 会留在当前目录,不会报错。这时候手动cd过去,或者重新建 worktree。

排查时记住一个原则:把“日志保存的内容”“终端恢复出的会话”“模型最后收到的上下文”当成三样东西。它们相互关联,但不一定完全相同。报错落在哪一层,就去查哪一层,别一上来就怀疑模型。

6. 把会话恢复用顺手的几个实操建议

会话恢复这套机制,用顺了能省很多重复劳动。几个我实际用下来觉得有用的点。

第一,给重要会话设标题和标签。custom-title和tag记录会进选择器,claude --resume不带参数时一眼就能找到。不然会话一多,光靠 session-id 根本认不出哪个是哪个。

第二,跨天任务别急着关终端就完事,确认一下当前 worktree 和分支。恢复时 worktree 目录还在才能切回去,目录没了就退回当前目录。养成习惯:收工前pwd和git branch --show-current看一眼,记下自己在哪。

第三,rewind 之后别慌。旧路线没被删,只是不在当前链里。你随时可以再 rewind 回去,或者从旧路线继续。JSONL 是追加写入,磁盘上两条路线都在,只是恢复时按parentUuid选一条。

第四,压缩过的会话,恢复后模型看到的是摘要加新消息,不是全部原始对话。如果你需要某个早期细节,别指望模型记得,直接翻终端历史或 JSONL 原文。摘要是有损的,这是设计如此,不是 bug。

第五,接入配置改完一定要重启验证。settings.json 里的 Base URL、Key、Model ID 三件套,改任何一个都要重启 Claude Code,然后跑一次claude --continue确认能正常恢复和请求。别改完直接接着用,环境变量没刷新会给你假象。

这套实现其实给了一个挺通用的工程思路:日志不一定只是写给人看的字符串。JSONL 还是文本,但每行有固定结构,程序能边跑边追加,也能事后重新解析。同一份日志还能存多种记录类型,对话、工具结果、工作状态各有各的 type,读取时交给不同逻辑处理。日志因此不只是排查问题的材料,也能成为恢复状态的数据来源。

Claude Code 就是靠这些结构化记录把会话重新接起来。你继续输入后,新的模型请求才开始准备。磁盘上保存的、终端恢复出的、模型收到的,三者相关但不相同——把这条线理清,恢复机制就不再神秘了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 6:50:32

HR10-7推拉自锁连接器:紧凑设备信号连接与维护效率优化

这几年做设备端的硬件集成&#xff0c;有一种感受越来越明显&#xff1a;设备体积一直在往下走&#xff0c;但接线密度和现场维护速度的要求却在往上走。普通的DB9太占面板空间&#xff0c;RJ45没有可靠锁固&#xff0c;工业环境里稍微振动就容易松脱&#xff0c;M8/M12虽然可靠…

作者头像 李华
网站建设 2026/10/3 6:50:31

定制工业线束全流程解析:材料选型、制造工艺与EMC测试

做工业设备这些年&#xff0c;几乎所有项目都绕不开一根“看起来很简单”的线束。主控板选好了&#xff0c;伺服电机定好了&#xff0c;钣金结构件也开模了&#xff0c;最后却常常卡在线束上&#xff1a;供应商报交期要八周&#xff0c;样品一装机就出现EMC不过、插头松动、线缆…

作者头像 李华
网站建设 2026/10/3 6:50:15

影刀RPA实战:读取Excel表格数据并循环处理每一项的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华