Claude Code JSONL Transcript — 完整学习指南

Claude Code JSONL Transcript --- 完整学习指南

基于 src/types/logs.tssrc/utils/sessionStorage.tssrc/hooks/useLogMessages.ts 源码分析。 实际 transcript 样本:~/.claude/projects/-Users-zhanghongkui-Documents-cc-haha-main/964aa807-*.jsonl


1. JSONL 文件是什么

每次与 Claude Code agent 对话,都会在以下路径产生一个 JSONL 文件:

javascript 复制代码
~/.claude/projects/<project-dir-hash>/<sessionId>.jsonl
  • 文件名 = sessionId(UUID,如 964aa807-99e0-46c8-a293-a9aed7702390
  • 格式 = 每行一个 JSON 对象(JSON Lines)
  • 写入时机 = 每次 REPL 渲染新消息后,通过 React useEffect 异步写入

2. 写入流程:在 TUI 交互的哪一步发生

scss 复制代码
用户输入 prompt 并回车
  │
  ▼
handlePromptSubmit()                  src/utils/handlePromptSubmit.ts:120
  │
  ▼
executeUserInput()                     src/utils/handlePromptSubmit.ts:396
  │
  ▼
processUserInput()                     src/utils/processUserInput/processUserInput.ts:85
  ├─ 斜杠命令 → processSlashCommand()
  ├─ Bash 模式 → processBashCommand()
  └─ 普通文本 → processTextPrompt() → createUserMessage()
  │
  ▼
onQuery()                              REPL.tsx 中定义
  │
  ▼
query() 生成器循环                     src/query.ts:219
  ├─ API 调用(通过 copilot-api 代理)
  ├─ 流式接收 text / thinking / tool_use
  ├─ 执行工具 → 发回结果
  └─ Turn 完成
  │
  ▼
setMessages(updatedMessages)           React state 更新
  │
  ▼
React 重新渲染 REPL
  │
  ▼
useLogMessages() useEffect 触发       src/hooks/useLogMessages.ts:19  ← ★ 关键触发点
  │
  ▼
recordTranscript(newMessages)          src/utils/sessionStorage.ts:1408
  ├─ 1. 去重:跳过已在 messageSet 中的 UUID
  ├─ 2. 清洗:cleanMessagesForLogging() 剥离仅 UI 的数据
  │
  ▼
Project.insertMessageChain()           src/utils/sessionStorage.ts:993
  ├─ 3. 首次 user/assistant 消息 → materializeSessionFile()
  │     创建 JSONL 文件,刷新缓冲的前导条目
  ├─ 4. 每条消息:加盖元数据戳(sessionId, cwd, version, gitBranch)
  │
  ▼
Project.appendEntry()                  src/utils/sessionStorage.ts:1128
  ├─ 5. 去重:UUID 已存在则跳过
  ├─ 6. 远程持久化:persistToRemote()(如果启用)
  │
  ▼
Project.enqueueWrite()                 src/utils/sessionStorage.ts:606
  ├─ 7. 推入内存写队列(最多缓冲 50 条或 1 秒后刷新)
  │
  ▼
drainWriteQueue() → appendToFile()    src/utils/sessionStorage.ts:645
  │
  ▼
appendEntryToFile()                    src/utils/sessionStorage.ts:2572
  └─ 8. fs.appendFileSync(path, jsonLine + '\n')  ← 实际磁盘写入

缓冲策略

scss 复制代码
enqueueWrite()          ← 立即返回 Promise(不阻塞 UI)
  │
  ▼
scheduleDrain()         ← 调度微任务(合并多次快速写入为一次刷新)
  │
  ▼
drainWriteQueue()       ← 条目批量打包成块(MAX_CHUNK_BYTES)
  │                       每个块 = 一次 fs.appendFileSync 调用
  ▼
appendToFile()          ← 实际的同步磁盘 I/O(在微任务上运行,不阻塞渲染帧)

配置参数:

  • 内存缓冲上限:50 条
  • 刷新间隔:1 秒
  • 条目先合并成大块再写入,减少 fs.appendFileSync 调用次数

3. JSONL 存在的五个目的

目的 1:会话恢复 (--resume / --continue)

这是最主要的原因。 JSONL 是恢复会话的唯一数据源。

  • loadFullLog() 读取 JSONL 文件
  • buildConversationChain() 沿 parentUuid 链表重建完整对话树
  • REPL 原样恢复------所有消息、工具调用、结果都被还原

没有这个文件,每次 Ctrl+C 或关闭终端都会丢失整个对话。

目的 2:Transcript 展示与搜索

  • /context --- 从 transcript 可视化 token 使用量
  • /export --- 导出对话到文件
  • Transcript 搜索(Ctrl+R)--- 索引和高亮匹配
  • 统计追踪 --- token 计数、turn 计数

目的 3:会话元数据与选择器

JSONL 存储不属于对话消息的元数据条目:

  • mode --- normal / plan 模式
  • permission-mode --- default / accept-edits / plan / bypass
  • ai-title --- 自动生成的会话标题
  • custom-title --- 用户通过 /rename 设置的名称
  • tag --- 会话标签
  • last-prompt --- 在 --resume 选择器中展示
  • pr-link --- 关联的 GitHub PR

目的 4:子 Agent 侧链持久化

AgentTool 生成子 agent 时,子 agent 的对话写入独立的侧链 JSONL

javascript 复制代码
<sessionId>/agents/<agentId>.jsonl

isSidechain 标志区分主线程和 agent 侧链消息:

typescript 复制代码
// src/utils/sessionStorage.ts:1224-1228
const isAgentSidechain = entry.isSidechain && entry.agentId !== undefined
const targetFile = isAgentSidechain
  ? getAgentTranscriptPath(asAgentId(entry.agentId!))
  : sessionFile

目的 5:远程持久化与多设备同步

当启用 ENABLE_SESSION_PERSISTENCE 时,每条条目也通过 persistToRemote() 发送到后端,实现跨机器会话连续性。


4. 所有 22 种 JSONL Entry 类型详解

完整的 Entry 联合类型(src/types/logs.ts:297):

typescript 复制代码
export type Entry =
  | TranscriptMessage        // 4 种子类型:user, assistant, attachment, system
  | SummaryMessage            // type: 'summary'
  | CustomTitleMessage        // type: 'custom-title'
  | AiTitleMessage            // type: 'ai-title'
  | LastPromptMessage         // type: 'last-prompt'
  | TaskSummaryMessage        // type: 'task-summary'
  | TagMessage                // type: 'tag'
  | AgentNameMessage          // type: 'agent-name'
  | AgentColorMessage         // type: 'agent-color'
  | AgentSettingMessage       // type: 'agent-setting'
  | PRLinkMessage             // type: 'pr-link'
  | FileHistorySnapshotMessage    // type: 'file-history-snapshot'
  | AttributionSnapshotMessage    // type: 'attribution-snapshot'
  | QueueOperationMessage     // type: 'queue-operation'
  | SpeculationAcceptMessage      // type: 'speculation-accept'
  | ModeEntry                 // type: 'mode'
  | WorktreeStateEntry        // type: 'worktree-state'
  | ContentReplacementEntry   // type: 'content-replacement'
  | ContextCollapseCommitEntry     // type: 'marble-origami-commit'
  | ContextCollapseSnapshotEntry   // type: 'marble-origami-snapshot'

外加一种在实际 transcript 中观察到但不在上述 union 中的类型:

  • permission-mode --- 权限模式变更通知

5. 分类详解

第一类:对话消息(4 种子类型)

这些是 TranscriptMessage 的子类型------它们携带 parentUuid 形成链表,buildConversationChain() 依靠此链表在 --resume 时重建对话。

user
属性
触发行为 用户在 TUI 中输入 prompt 并回车
代码路径 handlePromptSubmit()processUserInput()createUserMessage()recordTranscript()
包含字段 message.content(文本/图片块)、permissionModeparentUuidorigin.kind(human/bridge/queued_command)、promptSource
json 复制代码
{
  "type": "user",
  "parentUuid": "...",
  "uuid": "750ccbae-...",
  "message": {
    "role": "user",
    "content": "explain the architecture..."
  },
  "permissionMode": "default",
  "origin": { "kind": "human" },
  "promptSource": "typed",
  "userType": "external",
  "entrypoint": "cli",
  "cwd": "/Users/zhanghongkui/Documents/cc-haha-main",
  "sessionId": "964aa807-...",
  "version": "2.1.183",
  "gitBranch": "HEAD"
}

本质:你的每一次提问。


assistant
属性
触发行为 LLM 返回的每一次响应(包括中间 tool_use 步骤和最终文本回复)
代码路径 query() 循环 → API streaming → setMessages()useLogMessages()recordTranscript()
包含字段 message.contenttexttool_use 块)、message.usage(token 消耗)、message.stop_reasonmessage.model
json 复制代码
{
  "type": "assistant",
  "parentUuid": "750ccbae-...",
  "uuid": "b917be23-...",
  "message": {
    "role": "assistant",
    "content": [
      { "type": "text", "text": "Let me explore the codebase..." },
      { "type": "tool_use", "id": "toolu_...", "name": "codegraph_explore", "input": {...} }
    ],
    "usage": { "input_tokens": 50000, "output_tokens": 1200 },
    "stop_reason": "tool_use",
    "model": "deepseek-v4-pro"
  }
}

本质 :AI 的每一次回复。一个 turn 可能产生多条 assistant 条目(每轮 API 调用一条)。


system
属性
触发行为 系统注入的上下文信息,用于告知模型状态变化而非响应用户
代码路径 createSystemMessage()recordTranscript()
包含字段 message.content 包含 <system-reminder> 标签
json 复制代码
{
  "type": "system",
  "parentUuid": "...",
  "message": {
    "role": "user",
    "content": "<system-reminder>Content was truncated to fit context limit...</system-reminder>"
  }
}

本质:系统注入的提醒信息------内容截断通知、权限变更、工具搜索结果持久化提示等。


attachment
属性
触发行为 Agent/Tool/MCP 列表有变化时,增量或全量通告给模型
代码路径 getDeferredToolsDeltaAttachment() / getAgentListingDeltaAttachment() / getMcpInstructionsDeltaAttachment()createAttachmentMessage()
包含字段 attachment.type(agent_listing_delta / tool_listing_delta / mcp_instructions_delta)、attachment.addedTypesattachment.addedLinesattachment.removedTypesattachment.isInitial
json 复制代码
{
  "type": "attachment",
  "parentUuid": "750ccbae-...",
  "attachment": {
    "type": "agent_listing_delta",
    "addedTypes": ["claude", "Explore", "Plan", "general-purpose", ...],
    "addedLines": ["- claude: Catch-all for any task...", ...],
    "isInitial": true,
    "showConcurrencyNote": true
  }
}

本质:向模型通告可用工具、Agent 类型、MCP 指令的增量变更。


第二类:会话元数据(6 种类型)

这些条目没有 parentUuid ------不参与对话链表。由 reAppendSessionMetadata() 反复追加到 JSONL 末尾,保证它们在文件尾部 16KB 窗口内,以便 --resume 快速读取。

ai-title
属性
触发行为 Claude Code 自动基于首条用户消息生成会话标题
代码路径 AI 标题生成 → saveAiGeneratedTitle()appendEntryToFile()
优先级 低于 custom-title(用户重命名会覆盖 AI 标题)
json 复制代码
{
  "type": "ai-title",
  "sessionId": "964aa807-...",
  "aiTitle": "Understanding Claude Code Architecture"
}
custom-title
属性
触发行为 用户执行 /rename "新标题"
代码路径 /rename 命令 → saveCustomTitle()appendEntryToFile()
json 复制代码
{
  "type": "custom-title",
  "sessionId": "964aa807-...",
  "customTitle": "My Architecture Study"
}
last-prompt
属性
触发行为 每次用户提交新 prompt 后写入,覆盖前一个值
代码路径 insertMessageChain()this.currentSessionLastPrompt = ...reAppendSessionMetadata() → 写入
展示位置 claude --resume 选择器中展示(截断到 200 字符)
json 复制代码
{
  "type": "last-prompt",
  "sessionId": "964aa807-...",
  "lastPrompt": "explain the architecture of this project"
}
tag
属性
触发行为 用户执行 /tag <标签名>
代码路径 /tag 命令 → saveTag()appendEntryToFile()
agent-name / agent-color / agent-setting
属性
触发行为 为 Agent 设置自定义名称/颜色/配置
代码路径 /rename(agent 模式)或 Agent 设置变更

第三类:工具与权限状态(5 种类型)

mode
属性
触发行为 会话模式变更:normalcoordinator
代码路径 模式切换 → reAppendSessionMetadata()
json 复制代码
{
  "type": "mode",
  "sessionId": "964aa807-...",
  "mode": "normal"
}
permission-mode
属性
触发行为 权限模式变更,每次写入一条新记录
模式值 default(每次需确认)、acceptEdits(自动接受编辑)、bypassPermissions(跳过所有权限)、plan(计划模式)
json 复制代码
{
  "type": "permission-mode",
  "sessionId": "964aa807-...",
  "permissionMode": "default"
}

注意:此类型随 user/assistant 消息序列化,因此一次对话中会出现多次。

file-history-snapshot
属性
触发行为 每个 turn 结束时,对当前文件状态做快照
代码路径 executeUserInput()fileHistoryMakeSnapshot()insertFileHistorySnapshot()
用途 --resume 时恢复文件编辑历史;/context 展示文件变更
json 复制代码
{
  "type": "file-history-snapshot",
  "messageId": "750ccbae-...",
  "snapshot": {
    "messageId": "...",
    "trackedFileBackups": {},
    "timestamp": "2026-06-19T04:21:13.170Z"
  },
  "isSnapshotUpdate": false
}
content-replacement
属性
触发行为 大文件内容被替换为 stub(占位符)以节省 context window
代码路径 Content replacement 强制执行 → insertContentReplacement()
用途 Resume 时重放以保持 prompt cache 稳定性
attribution-snapshot
属性
触发行为 追踪 Claude Code 对每个文件贡献的字符数
用途 Git commit 归属追踪(哪些字符是 Claude 写的)

第四类:高级功能(6 种类型,大部分仅 ant 内网可见)

summary
属性
触发行为 手动 /compact 或自动 compact 时生成对话摘要
代码路径 compactConversation() → 生成 summary → appendEntry()
用途 存储 compact 时生成的摘要
task-summary
属性
触发行为 后台定期 fork 主线程,生成当前任务的摘要
触发频率 每 5 步或每 2 分钟
用途 claude ps 展示当前 agent 正在做什么
queue-operation
属性
触发行为 命令队列有操作变更(入队/出队/取消)
代码路径 insertQueueOperation()
speculation-accept
属性
触发行为 推测性 token 生成被接受(性能优化)
用途 追踪推测加速节省的时间
worktree-state
属性
触发行为 进入/退出 git worktree 时持久化状态
代码路径 EnterWorktree / ExitWorktreesaveWorktreeState()
json 复制代码
{
  "type": "worktree-state",
  "sessionId": "...",
  "worktreeSession": {
    "originalCwd": "...",
    "worktreePath": "...",
    "worktreeName": "...",
    "worktreeBranch": "...",
    "sessionId": "..."
  }
}
属性
触发行为 将会话关联到 GitHub Pull Request
代码路径 PR 关联操作 → linkSessionToPR()
marble-origami-commit / marble-origami-snapshot
属性
触发行为 Context Collapse(上下文折叠)系统的持久化
代码路径 recordContextCollapseCommit() / recordContextCollapseSnapshot()
用途 恢复折叠的上下文段------commit 是追加的(回放所有),snapshot 是最后写入生效

6. 实际 Transcript 样本分析

你的 transcript 964aa807-...jsonl 的类型分布:

sql 复制代码
类型                    出现次数    说明
─────────────────────────────────────────────────────
assistant               64       AI 的每次响应(含工具调用的中间步骤)
user                    35       你的每次输入
permission-mode         15       权限模式(随消息序列化写入)
mode                    15       会话模式(随元数据刷新重复写入)
ai-title                15       AI 生成的标题(每次元数据刷新重写)
last-prompt             14       最近一次 prompt(每次元数据刷新重写)
system                   7       系统注入提醒
file-history-snapshot    7       每个 turn 结束的文件快照
attachment               6       工具/Agent/MCP 列表通告

未出现在你 transcript 中的类型(需要特定操作触发):

  • custom-title --- 需要执行 /rename
  • tag --- 需要执行 /tag
  • agent-name/color/setting --- 需要 Agent 配置
  • summary --- 需要 compact
  • task-summary --- 需要 ant 内网
  • queue-operation --- 需要 ant 内网
  • speculation-accept --- 需要 ant 内网
  • worktree-state --- 需要 worktree 操作
  • pr-link --- 需要关联 PR
  • attribution-snapshot --- 需要 ant 内网
  • content-replacement --- 需要 ant 内网
  • marble-origami-* --- 需要 ant 内网

7. 去重与链表机制

parentUuid 链表

每条 userassistantsystemattachment 消息都携带 parentUuid,指向前一条链参与者消息:

php 复制代码
user (uuid: A, parentUuid: null)
  ↑
assistant (uuid: B, parentUuid: A)
  ↑
assistant (uuid: C, parentUuid: B)    ← tool_use 响应
  ↑
user (uuid: D, parentUuid: C)         ← tool_result
  ↑
assistant (uuid: E, parentUuid: D)    ← 继续
  ↑
...

--resumebuildConversationChain() 沿链表从叶子走到根,重建完整对话。

UUID 去重

appendEntry() 在写入前检查 UUID 是否已存在:

typescript 复制代码
// src/utils/sessionStorage.ts:1242-1243
const isNewUuid = !messageSet.has(entry.uuid)
if (isAgentSidechain || isNewUuid) {
  void this.enqueueWrite(targetFile, entry)
  if (!isAgentSidechain) {
    messageSet.add(entry.uuid)
  }
}

isChainParticipant 过滤

并非所有消息都参与链表。isChainParticipant() 过滤掉进度消息和临时事件,只有 userassistantattachmentsystem 以及 compact boundary 消息才更新 parentUuid 指针。


8. 写入优先级总结

perl 复制代码
┌──────────────────────────────────────────────────────┐
│  高频写入(每条消息都写)                              │
│  ├─ user / assistant / system / attachment           │
│  ├─ permission-mode(随消息序列化)                    │
│  └─ file-history-snapshot(每个 turn 结束)            │
│                                                      │
│  中频写入(每个 turn / compact 时写)                  │
│  ├─ last-prompt(覆盖写入)                            │
│  └─ mode(切换时写)                                   │
│                                                      │
│  低频写入(用户主动操作时写)                           │
│  ├─ custom-title, tag(/rename, /tag)                │
│  ├─ agent-name, agent-color, agent-setting            │
│  ├─ summary(compact 时)                              │
│  ├─ pr-link(关联 PR 时)                              │
│  └─ worktree-state(进入/退出 worktree 时)            │
│                                                      │
│  仅在 ant 内网(USER_TYPE === 'ant')                   │
│  ├─ task-summary(后台 fork 生成)                     │
│  ├─ queue-operation(命令队列变更)                     │
│  ├─ speculation-accept(推测加速追踪)                  │
│  ├─ content-replacement(内容替换持久化)               │
│  ├─ attribution-snapshot(代码归属追踪)                │
│  └─ marble-origami-commit / snapshot(上下文折叠)     │
└──────────────────────────────────────────────────────┘

9. 关键源文件索引

文件 内容
src/types/logs.ts 所有 Entry 类型定义(22 种)
src/utils/sessionStorage.ts JSONL 写入核心逻辑:appendEntry()insertMessageChain()recordTranscript()drainWriteQueue()appendEntryToFile()
src/hooks/useLogMessages.ts React hook ------ 监听 messages\[\] 变化并触发 recordTranscript()
src/screens/REPL.tsx REPL 组件 ------ 调用 useLogMessages()
src/utils/handlePromptSubmit.ts 用户输入处理 ------ handlePromptSubmit()executeUserInput()
src/utils/processUserInput/processUserInput.ts 输入路由 ------ text / slash / bash 命令
src/query.ts 核心 query 生成器循环
相关推荐
葫芦和十三7 小时前
多模态融合|是数据形态工程,不是 Prompt 工程
openai·agent·ai编程
不好听6137 小时前
Tool:让大模型长出手脚
llm·agent
用户329901675058 小时前
给 AI 返回数据加 TS 类型,别全标 any
agent
冬奇Lab20 小时前
Agent 系列(22):Context Engineering 深度——三种上下文管理策略的量化对比
人工智能·agent
葫芦和十三21 小时前
渐进发现|代码库不是文档库
langchain·agent·ai编程
米小虾1 天前
告别单打独斗:2026年多Agent协作架构实战指南
人工智能·agent
EternalRights1 天前
skill 的终局是 agent 化:我给 SKILL.md 写了个编译器,6 个 Harness 把散文孵化成独立 Agent
agent
leeyi1 天前
Document 组件:把文件喂给 AI 之前,必须先做这三步
aigc·agent·ai编程
言川9451 天前
【万字长文】手搓一个Agent教程
agent