Claude Code · 持久化机制现场解剖

会话存进磁盘,
环境每次重生。

拿到一个 session 文件(.jsonl),你能从里面还原出多少当时的运行环境?逐行拆开后, 结论反直觉:transcript 只忠实记录对话本身,几乎不记录运行环境。 工具的参数 schema、当前的 CLAUDE.md、系统提示词都不落盘 —— 它们由 system-reminder 在 每次请求时动态注入、发完即弃。所以这些信息,从 session 文件里读不回来。

2真实 session 逐行剖析
312个 transcript 跨库统计
0处存下 system prompt
§ 00 — 研究问题

想完整重建当时的上下文,这三样环境信息缺一不可。 可从 session 文件里,它们还原得出来吗?

问题 01

MCP 工具的描述

所有挂载的工具,它们的参数 schema 和说明文字,能从 transcript 里还原吗?

问题 02

Skill 列表与描述

当前可用的全部 skill,连同它们的 description,还原得出来吗?

问题 03

当前的 CLAUDE.md

注入到上下文里的项目说明,作为环境的一部分,它被记下来了吗?

直觉说:既然换台机器也能 resume,这些环境信息必然被写进了 JSONL,等着被读回来。 逐行拆开后,三个答案是:只还原得出名字、名单加描述能还原、完全还原不出。

仅工具名可还原

MCP 工具描述

299 deferred_tools_delta / 全库
真实 transcript 片段
c75e2b9b…463d.jsonl · line 4 attachment
{
  "type": "attachment",
  "attachment": {
    "type": "deferred_tools_delta",
    "addedNames": [
      "CronCreate", "EnterPlanMode",
      "Monitor", "TaskCreate",
      "WebFetch", "WebSearch"  … 仅工具名
    ],
    "pendingMcpServers": [
      "plugin:chrome-devtools-mcp:chrome-devtools"
    ]
  }
}
// 整行里没有任何 description / inputSchema 字段
addedNames 只有一串工具名字。没有参数定义,没有一个字的说明。
pendingMcpServers 记的是还没连上的 MCP server,本质是连接状态,不是工具内容。
分析与判决

完整的工具描述从不落盘。transcript 里只留下「有哪些工具可调」这张名单。

真正的参数 schema 和说明文字,是运行时由 ToolSearch 按需拉取的, 用完即弃。所以一个工具长什么样、怎么调,永远是当下从注册表现查, 而不是从历史里读出来。

这也解释了为什么 deferred_tools_delta 是 delta(增量):它只在工具集 发生变化时追加一行,记录这一刻多了哪些、少了哪些名字。它是一份变更日志, 不是工具的权威定义。

能还原:某一刻活跃的工具名单。还原不出:参数 schema 与说明 —— 全库 299 条 delta 无一携带。

名单 + 描述可还原

Skill 列表

297 skill_listing / 全库
真实 transcript 片段
496506bc…2732.jsonl · line 6 attachment
{
  "type": "attachment",
  "attachment": {
    "type": "skill_listing",
    "skillCount": 60,
    "isInitial": true,
    "names": ["caveman", "diagnose", "handoff" …×60],
    "content": "- caveman: Ultra-compressed communication…\n
              - diagnose: Disciplined diagnosis loop…\n
              - handoff: Compact the conversation into
                       a handoff document…\n
              - prototype: Build a throwaway prototype…"
  }
}
// 每个 skill 的一行描述都在,只是不含 SKILL.md 正文
content 名单,加上每个 skill 完整的一行描述,都写进了 transcript。
SKILL.md 唯独不含各 skill 的完整正文 — 正文按需加载,这是预期行为,不是缺失。
分析与判决

三个问题里,只有 skill 列表把内容真正写进了磁盘 — 而且写得很全。

content 是一段拼好的 Markdown:每个 skill 用于触发判断的那行描述 (来自 frontmatter)都完整在列,一个不少names 给名单, content 给描述。

它唯一没带上的,是各 skill 的完整 SKILL.md 正文 — 而这正是预期行为。 正文只在某个 skill 真正激活时才按需读取,没必要在开场就全塞进上下文。所以这份 listing 不是被截断的影子,而是分层加载里恰到好处的那一层:描述齐全,足够判断该不该用。

能还原:完整名单 + 每个 skill 的一行描述(全库 297 条)。还原不出: SKILL.md 正文 —— 按需加载,本就不在文件里。

完全不可还原

当前的 CLAUDE.md

4 / 312 命中 'claudeMd' 的文件
它出现的唯一形态
第一条 user 消息内 · 启动时拼接 system-reminder
<system-reminder>
# claudeMd
Contents of /…/code-journal/CLAUDE.md
(project instructions, checked into the codebase):

# code-journal
Open-source tool to collect AI coding-agent
sessions and turn them into work reports…
</system-reminder>

// 它不是独立的一行类型。每次请求现读磁盘,
// 现拼进 message #1,发完即丢,不单独留档。
自我指涉 312 个文件里只 4 个命中 claudeMd — 而那 4 个正是研究本课题留下的 session,关键词出现在 grep 的 tool_result 里。
分析与判决

CLAUDE.md 没有自己的行类型,没有自己的 attachment。它根本不在 transcript 里。

它以 system-reminder 的形式,在每次请求时现读磁盘, 现拼进第一条 user 消息的开头,随这条消息发给模型,发完即丢。 JSONL 里不为它单独留一行档案。

最有力的证据是那个自我指涉的巧合:全库唯一能搜到 claudeMd 的, 是几个正在研究这个问题的会话,因为它们 grep 过自己。 对照本页 — 你现在读到的 CLAUDE.md 内容,也来自这样一条即时注入的 reminder。

可还原性为零:文件里没有它的一个字节。而 resume 照常带着 CLAUDE.md 工作 — 那它必然是每次重新生成的。

§ 04 — 换个视角

一个 session,
到底能告诉你什么?

别问「环境存在哪」,问「从一个 transcript 里到底读得出什么」。答案很干脆:只读得出 不变的对话;环境每次请求动态构建,从来不在文件里。

JSONL 读得出

不变信息

已发生、不重算 — 唯一落盘的

  • user / assistant 每一轮消息
  • 每一次工具调用与结果
  • thinking、token 用量、时间戳

transcript 是对话的忠实日志。能从它可靠读出的,只有这些「不会再变」的东西。

session 里读不到

环境上下文

每次请求动态注入 — 不留存

  • System promptCLI 版本
  • 工具参数 schemaToolSearch
  • CLAUDE.md 内容磁盘
  • Skill 完整正文SKILL.md
  • 当前日期、MCP 连接运行时

由 system-reminder 在每次请求时即时拼装、注入、用完即弃。文件里偶尔出现的 skill_listing、tools 增量只是某一刻的注入快照,不是当前环境的可信副本。

所以一个 session 答得了「聊了什么」,答不了「环境是什么样」。 环境每次请求重新拼 — 这正是 resume 照常工作、且自动套用当下环境的原因。

§ 05 — 普查

全库 12 种 attachment 子类型

打开任意一个 session,你会遇到的 attachment 记录类型就这 12 种 —— 元数据的「可还原面」到此为止。跨 312 个 transcript 的出现次数,前两名印证了结论:起手就记名单,从不记内容。

deferred_tools_delta
299
skill_listing
297
task_reminder
169
hook_success
73
queued_command
14
workflow_keyword_request
11
date_change
9
goal_status
8
edited_text_file
6
file
5
command_permissions
5
invoked_skills
1
给研究者的一句话

想还原环境,去现场,别翻档案

session 文件是对话的忠实档案,却是环境的盲区。当时挂着哪些工具、哪版 CLAUDE.md、 哪些 skill —— 这些都不在文件里留存,逐行翻遍也读不回来。

唯一的还原路径是回到运行现场:当时的磁盘、当时的 CLI 版本、当时活动的 MCP 连接。 而它们可能早已漂移 —— 这也正是为什么 resume 一个旧会话,模型读到的是此刻的环境, 而非当初那一份。

样本 · c75e2b9b(7 行)+ 496506bc(52 行) 统计 · 312 transcripts 项目 · code-journal