主题
Claude Code transcript.jsonl 详解
一、什么是 transcript.jsonl?
transcript.jsonl 是 Claude Code 的会话记录文件,完整记录了每次 Claude Code 会话中发生的一切:用户的每个提问、Claude 的每个回复、每次工具调用的输入和输出、扩展思考(thinking)过程、token 使用量、git 状态快照等。
核心特点
| 特性 | 说明 |
|---|---|
| 格式 | JSONL(JSON Lines)— 每行一个完整的 JSON 对象 |
| 写入方式 | 纯追加(append-only),每产生一条消息就写入一行 |
| 存储位置 | ~/.claude/projects/<编码后的项目路径>/<session-uuid>.jsonl |
| 数据完整性 | 不是摘要、不是日志,而是逐消息的完整记录 |
| 无外部依赖 | 没有数据库、没有索引,全部是扁平文件 |
它记录了什么?
- 你输入的每个 prompt
- Claude 的每个文本回复
- Claude 的扩展思考(thinking)过程
- 每次工具调用(文件读写、Bash 命令、搜索等)的完整输入和输出
- 子代理(subagent)的创建和结果
- token 消耗量和缓存命中情况
- 会话压缩(compaction)检查点
- git 工作区状态快照
- 系统提示词(system prompt)
注意:system prompt 只在 transcript 文件的首条
system记录中保存,hook 无法直接访问,但读取 transcript 文件即可获取。
二、文件存储结构
目录布局
~/.claude/
├── history.jsonl ← 全局命令历史索引
├── projects/
│ └── <url-encoded-project-path>/ ← 项目路径编码后作为目录名
│ └── <session-uuid>.jsonl ← 每个会话一个文件
├── tasks/
│ └── <session-id>/ ← 任务列表
│ ├── 1.json
│ └── 2.json
├── plans/
│ └── <plan-name>.md ← 计划文件
└── teams/
└── <team-name>.json ← 团队配置路径编码规则
项目路径中的 / 被替换为 -:
| 实际项目路径 | 编码后的目录名 |
|---|---|
/home/user/myapp | -home-user-myapp |
/Users/kyle/code/electric | -Users-kyle-code-electric |
文件命名
每个会话文件以会话 UUID 命名,例如:
~/.claude/projects/-home-user-myapp/00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonlhistory.jsonl 全局索引
~/.claude/history.jsonl 是全局历史索引,记录所有会话的元数据摘要(每行一个 JSON):
json
{
"prompt": "帮我写一个计算阶乘的函数",
"timestamp": "2026-04-20T10:30:00.000Z",
"projectPath": "/home/user/myapp",
"sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0"
}/resume 命令就是扫描这个索引和对应目录,按修改时间排序后展示给你选择。
三、JSONL 消息格式(Envelope 结构)
每行 JSON 对象共享一个信封结构(envelope),核心公共字段如下:
json
{
"type": "assistant",
"uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"parentUuid": "1a2b3c4d-5678-...",
"timestamp": "2026-02-20T09:14:32.441Z",
"sessionId": "abc123",
"cwd": "/home/user/myapp",
"version": "2.1.100",
"gitBranch": "main",
"isSidechain": false,
"userType": "external",
"message": { ... }
}公共字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
type | string | 消息类型:user、assistant、system、tool_result、summary、result、file-history-snapshot、queue-operation |
uuid | string | 本条消息的唯一标识(UUID) |
parentUuid | string | null | 父消息的 UUID,构成有向无环图(DAG)结构 |
timestamp | string | ISO 8601 时间戳 |
sessionId | string | 会话 ID |
cwd | string | 消息产生时的当前工作目录 |
version | string | Claude Code 版本号 |
gitBranch | string | 当前 Git 分支(不在仓库中为空字符串) |
isSidechain | boolean | 是否为侧链消息(分支对话) |
userType | string | 通常为 "external" |
message | object | 消息体,结构因 type 不同而异 |
parentUuid — DAG 而非线性列表
parentUuid 是 transcript 的关键设计。每条消息指向它所响应的消息,形成一个有向无环图(DAG)。当对话分支——探索替代方案、重试失败的工具调用、或并发运行子代理——每个分支都有自己的消息链。
用户消息 A (uuid: "aaa", parentUuid: null)
├── Claude 回复 B (uuid: "bbb", parentUuid: "aaa")
│ ├── 工具结果 C (uuid: "ccc", parentUuid: "bbb")
│ └── 工具结果 D (uuid: "ddd", parentUuid: "bbb") ← 并行工具调用
└── Claude 回复 E (uuid: "eee", parentUuid: "aaa") ← 分支/重试四、七种核心消息类型
4.1 user — 用户消息
包含你输入的 prompt、hook 注入的上下文、系统说明、或工具结果的回传。
json
{
"type": "user",
"uuid": "abc-123",
"parentUuid": null,
"timestamp": "2026-02-20T09:14:28.000Z",
"sessionId": "session-001",
"cwd": "/home/user/myapp",
"version": "2.1.100",
"gitBranch": "main",
"message": {
"role": "user",
"content": "Add input validation to the createUser endpoint"
}
}message.content 的两种形式:
- 字符串:用户直接输入的 prompt 文本
- 数组:工具结果回传时,包含
tool_result类型的内容块
用户记录还可细分为以下子类型(可通过上下文推断):
| 子类型 | 来源 |
|---|---|
| user prompt | 你实际输入的文本 |
| command | 斜杠命令如 /help、/model |
| command_output | 命令的输出注入 |
| hook_result | UserPromptSubmit hook 注入的上下文 |
| system_caveat | Claude Code 内部注入的系统说明 |
4.2 assistant — Claude 的回复
信息密度最高的类型。包含 Claude 一次完整 turn 的所有内容:文本回复、工具调用、内部思考。content 是一个内容块数组。
json
{
"type": "assistant",
"uuid": "def-456",
"parentUuid": "abc-123",
"requestId": "req_01abc...",
"message": {
"role": "assistant",
"model": "claude-sonnet-4-6-20260401",
"id": "msg_01xyz...",
"type": "message",
"content": [
{
"type": "thinking",
"thinking": "用户想要在 createUser 端点添加验证。我应该先检查他们已有的验证库..."
},
{
"type": "text",
"text": "我将使用你的代码库中已有的 zod schema 模式来添加输入验证。"
},
{
"type": "tool_use",
"id": "toolu_01abc",
"name": "Read",
"input": {
"file_path": "/home/user/myapp/src/routes/users.ts"
}
}
],
"usage": {
"input_tokens": 12840,
"output_tokens": 631,
"cache_read_input_tokens": 8200,
"cache_creation": {
"ephemeral_5m_input_tokens": 3600,
"ephemeral_1h_input_tokens": 0
}
},
"stop_reason": "tool_use"
}
}三种内容块(Content Block)
| 类型 | 字段 | 说明 |
|---|---|---|
text | text | Claude 给你的可见文字回复 |
tool_use | id, name, input | 工具调用。包含工具名、唯一 ID、完整输入参数。每次 Bash 命令、文件读写、Web 搜索、MCP 调用、子代理创建都会记录 |
thinking | thinking | 扩展思考。Claude 的内部推理过程,展示了它为什么做出每个决定。只在启用了扩展思考的模型(如 Opus)上出现 |
usage — Token 使用量
每个 assistant turn 都包含精确的 token 计量:
| 字段 | 说明 |
|---|---|
input_tokens | 输入 token 数 |
output_tokens | 输出 token 数 |
cache_read_input_tokens | 缓存命中的 token 数(节省成本) |
cache_creation.ephemeral_5m_input_tokens | 5 分钟缓存写入 |
cache_creation.ephemeral_1h_input_tokens | 1 小时缓存写入 |
stop_reason — 停止原因
| 值 | 含义 |
|---|---|
end_turn | Claude 自然结束回复 |
tool_use | Claude 需要调用工具,等待工具结果后继续 |
max_tokens | 达到最大输出 token 限制 |
4.3 tool_result — 工具返回结果
每个 tool_use 内容块之后,工具输出以 tool_result 记录回传。通过 tool_use_id 关联:
json
{
"type": "tool_result",
"uuid": "ghi-789",
"parentUuid": "def-456",
"toolUseResult": {
"tool_use_id": "toolu_01abc",
"content": "import { z } from 'zod';\n\nexport const createUserSchema = z.object({\n email: z.string().email(),\n...",
"is_error": false
}
}| 字段 | 说明 |
|---|---|
tool_use_id | 对应 assistant 消息中 tool_use 块的 id |
content | 工具的完整输出(文件内容、命令 stdout/stderr、搜索结果等) |
is_error | 是否为错误状态 |
工具结果存储完整内容:整个读取的文件、完整的 Bash 输出、全部 MCP 搜索结果。这也是为什么 transcript 文件可能非常大的原因。
4.4 system — 系统消息
会话文件中的第一条记录通常是 system 消息,包含 Claude 在会话启动时收到的完整系统 prompt:工具定义、权限模式、项目上下文、注入的 CLAUDE.md 内容、MCP 服务器指令等。
json
{
"type": "system",
"subtype": "local_command",
"parentUuid": null,
"isSidechain": false,
"sessionId": "session-001",
"version": "2.1.100",
"message": {
"role": "system",
"content": "You are Claude Code, an AI coding assistant..."
}
}system 记录还有其他子类型,如 stop_hook_summary(Stop hook 执行摘要),包含以下额外字段:
| 字段 | 说明 |
|---|---|
hookCount | hook 执行数量 |
hookInfos | hook 详细信息 |
hookErrors | hook 错误信息 |
preventedContinuation | 是否阻止了继续 |
stopReason | 停止原因 |
4.5 summary — 压缩检查点
当会话上下文窗口接近限制时,Claude Code 会压缩对话:将较旧的轮次总结后替换为压缩版本。压缩摘要存储为 summary 记录。
json
{
"type": "summary",
"summary": "用户请求为 createUser 端点添加输入验证。已使用 zod 实现。",
"leafUuid": "uuid-of-final-message"
}| 字段 | 说明 |
|---|---|
summary | 人类可读的会话标题/摘要 |
leafUuid | 最终消息的 UUID |
summary记录通常也作为文件的第一行出现,作为会话的标题和入口。
4.6 result — 会话完成标记
已完成会话的最后一条记录。包含会话结果、任务是否成功或被中断、最终成本摘要等。
4.7 file-history-snapshot — Git 状态快照
在会话启动时记录,捕获工作目录的 git 状态:暂存的变更、未暂存的变更、未跟踪的文件。作为会话期间所有文件变更的基线。
json
{
"type": "file-history-snapshot",
"messageId": "snapshot-uuid",
"snapshot": {
"messageId": "snapshot-uuid",
"trackedFileBackups": { ... },
"timestamp": "2026-02-20T09:14:25.000Z"
}
}4.8 queue-operation — 队列操作
记录消息队列的入队/出队操作:
json
{
"type": "queue-operation",
"operation": "enqueue",
"timestamp": "2026-02-20T09:14:30.000Z",
"sessionId": "session-001",
"content": "用户输入的文本..."
}五、assistant 内容块中的工具调用详解
tool_use 内容块记录了 Claude 调用的每一种工具。以下是常见工具及其 input 结构:
5.1 Bash — Shell 命令
json
{
"type": "tool_use",
"id": "toolu_01abc",
"name": "Bash",
"input": {
"command": "npm test",
"description": "Run the test suite",
"timeout": 120000
}
}5.2 Read — 文件读取
json
{
"type": "tool_use",
"id": "toolu_02def",
"name": "Read",
"input": {
"file_path": "/home/user/myapp/src/utils.ts",
"offset": 1,
"limit": 100
}
}5.3 Write — 文件写入
json
{
"type": "tool_use",
"id": "toolu_03ghi",
"name": "Write",
"input": {
"file_path": "/home/user/myapp/src/validators.ts",
"content": "import { z } from 'zod';\n\nexport const schema = ..."
}
}5.4 Edit — 文件编辑
json
{
"type": "tool_use",
"id": "toolu_04jkl",
"name": "Edit",
"input": {
"file_path": "/home/user/myapp/src/routes.ts",
"old_string": "app.post('/users', handler)",
"new_string": "app.post('/users', validateInput, handler)"
}
}5.5 Grep / Glob — 搜索
json
{
"type": "tool_use",
"id": "toolu_05mno",
"name": "Grep",
"input": {
"pattern": "createUser",
"path": "/home/user/myapp/src"
}
}5.6 Task — 子代理创建
json
{
"type": "tool_use",
"id": "toolu_06pqr",
"name": "Task",
"input": {
"subagent_type": "Explore",
"description": "Find all authentication middleware",
"prompt": "Search for all middleware that validates JWT tokens..."
}
}5.7 WebFetch / WebSearch — 网络操作
json
{
"type": "tool_use",
"id": "toolu_07stu",
"name": "WebSearch",
"input": {
"search_term": "zod validation best practices",
"explanation": "Looking for validation patterns"
}
}六、会话中的数据流动
一个典型的交互在 transcript 中的记录顺序:
第 1 行: summary ← 会话标题(首次回复后生成)
第 2 行: system ← 完整 system prompt(工具定义、CLAUDE.md 等)
第 3 行: file-history-snapshot ← Git 状态快照
第 4 行: user ← 你的第一个 prompt
第 5 行: assistant ← Claude 的思考 + 回复 + 工具调用
content: [thinking, text, tool_use, tool_use]
第 6 行: user ← 工具结果回传(tool_result 数组)
第 7 行: assistant ← Claude 基于工具结果继续回复
content: [thinking, text]
第 8 行: user ← 你的第二个 prompt
第 9 行: assistant ← ...
...
第 N 行: summary ← 压缩检查点(如果上下文窗口满了)
第 N+1: result ← 会话完成标记工具调用的完整链路
assistant (content: [..., {type: "tool_use", id: "toolu_01", name: "Read", input: {...}}])
│
▼ Claude Code 执行工具
│
user (content: [{type: "tool_result", tool_use_id: "toolu_01", content: "文件内容...", is_error: false}])
│
▼ Claude 看到工具结果后继续推理
│
assistant (content: [{type: "thinking", thinking: "..."}, {type: "text", text: "..."}])注意:工具结果虽然是 Claude Code 自动回传的,但在 transcript 中记录为
type: "user"的消息(因为在 API 层面,工具结果作为 user role 消息发送)。
七、子代理与团队模式
子代理会话
当 Claude 通过 Task 工具创建子代理时,子代理运行在独立的 JSONL 文件中,有自己完整的消息历史。
sessions/
├── main-session-uuid.jsonl ← 主会话
└── main-session-uuid/
└── subagents/
└── agent-def456.jsonl ← 子代理会话主会话中的 tool_use(name: "Task")与子代理的 JSONL 文件通过以下元数据关联:
| 字段 | 位置 | 说明 |
|---|---|---|
parentToolUseId | 子代理会话 | 创建此子代理的 tool call ID |
agentId | 子代理会话 | 子代理的唯一标识 |
agentType | 子代理会话 | 代理类型(如 Explore、Bash) |
团队模式
Claude Opus 及更高版本支持团队模式,一个领导代理协调多个专家代理并行工作:
sessions/
├── team-lead-uuid.jsonl ← 编排者
├── teammate-explore-uuid.jsonl ← 文件探索专家
├── teammate-search-uuid.jsonl ← 代码搜索专家
├── teammate-plan-uuid.jsonl ← 计划生成专家
└── teammate-bash-uuid.jsonl ← 执行专家每个文件都是完整且独立的,可以单独阅读。协调通过领导代理中的 Task 工具调用实现。
八、Token 经济与成本分析
每个 assistant turn 的 usage 字段提供了精确的 token 计量,可用于逐轮成本分析:
Turn 1 (探索): 15,240 input | 842 output | 8,100 cache hit
Turn 2 (规划): 3,820 input | 1,240 output | 12,000 cache hit
Turn 3 (实现): 4,100 input | 2,890 output | 13,500 cache hit- 缓存读取 token 通常按基础输入价格的 10% 计费
- 高缓存命中数意味着 Claude 在高效复用之前处理过的上下文(CLAUDE.md、工具定义、早期对话轮次)
九、实用操作
9.1 查找最近的 transcript 文件
bash
ls -lt ~/.claude/projects/*/*.jsonl | head -109.2 查看会话中的消息类型分布
bash
cat session.jsonl | jq -r '.type' | sort | uniq -c | sort -rn9.3 提取用户和 Claude 的对话(跳过工具调用细节)
python
import json
def extract_conversation(jsonl_path):
messages = []
with open(jsonl_path) as f:
for line in f:
event = json.loads(line)
if event.get("type") == "user":
content = event.get("message", {}).get("content", "")
if isinstance(content, str) and content:
messages.append({"role": "user", "text": content})
elif event.get("type") == "assistant":
content = event.get("message", {}).get("content", [])
text_parts = [
block["text"]
for block in content
if isinstance(block, dict) and block.get("type") == "text"
]
if text_parts:
messages.append({"role": "assistant", "text": "\n".join(text_parts)})
return messages9.4 统计工具调用次数
bash
cat session.jsonl | jq -r '
select(.type == "assistant") |
.message.content[]? |
select(.type == "tool_use") |
.name
' | sort | uniq -c | sort -rn9.5 计算会话总 token 消耗
bash
cat session.jsonl | jq -r '
select(.type == "assistant") |
.message.usage |
select(. != null) |
"\(.input_tokens) \(.output_tokens)"
' | awk '{input+=$1; output+=$2} END {print "Input:", input, "Output:", output, "Total:", input+output}'9.6 提取所有 Bash 命令
bash
cat session.jsonl | jq -r '
select(.type == "assistant") |
.message.content[]? |
select(.type == "tool_use" and .name == "Bash") |
.input.command
'9.7 查看 Claude 的思考过程
bash
cat session.jsonl | jq -r '
select(.type == "assistant") |
.message.content[]? |
select(.type == "thinking") |
.thinking
'9.8 通过 Hook 增量读取 transcript
在 Stop hook 中增量读取新增的 transcript 行(避免每次都读取完整文件):
python
import json
import os
STATE_FILE = os.path.expanduser("~/.claude/state/hook_state.json")
def load_state():
try:
with open(STATE_FILE) as f:
return json.load(f)
except FileNotFoundError:
return {}
def save_state(state):
os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
with open(STATE_FILE, "w") as f:
json.dump(state, f)
def read_new_lines(transcript_path, session_id):
state = load_state()
offset = state.get(session_id, {}).get("offset", 0)
with open(transcript_path, "rb") as f:
f.seek(offset)
new_data = f.read()
new_offset = f.tell()
state.setdefault(session_id, {})["offset"] = new_offset
save_state(state)
new_lines = []
for line in new_data.decode("utf-8", errors="replace").strip().split("\n"):
if line:
try:
new_lines.append(json.loads(line))
except json.JSONDecodeError:
pass
return new_lines十、/resume 工作原理
/resume 命令的工作流程:
- 识别当前项目 — 根据当前工作目录
- 扫描文件 — 列出
~/.claude/projects/<编码路径>/下所有.jsonl文件 - 按修改时间排序 — 最近修改的排在前面
- 展示选择 — 显示
summary行中的标题和时间 - 加载会话 — 选择后顺序读取 JSONL 文件,重建完整对话上下文
- 恢复状态 — 处理每条 user、assistant、tool_result 记录,恢复到之前的对话点
这就是为什么 /resume 能无缝继续几天甚至几周前的对话——完整历史保存在 JSONL 文件中。
十一、会话保留与配置
默认保留策略
- 默认保留 30 天
- 可通过配置延长:
json
{
"logRetentionDays": 99999
}跳过记录
设置环境变量 CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 可跳过写入 prompt 历史和会话记录。使用此变量启动的会话不会出现在 --resume、--continue 或上箭头历史中。
十二、常见问题
transcript 文件太大怎么办?
工具结果存储完整内容(整个文件、完整 Bash 输出),一个复杂会话可能有数万行。建议:
- 使用增量读取(参考 9.8 节)
- 使用
jq过滤只关心的消息类型 - 利用
summary记录快速了解会话概况
为什么 tool_result 的 type 是 "user"?
在 Anthropic Messages API 层面,工具结果作为 user role 消息发送回模型。这是 API 协议决定的,不影响语义理解。
如何区分用户真正的输入和工具结果回传?
- 用户输入:
message.content是字符串 - 工具结果:
message.content是数组,包含tool_result类型的对象
transcript 中有 system prompt 吗?
有。会话文件的第一条 system 类型记录包含完整的系统 prompt。但 hook 的 stdin JSON 中不直接包含 system prompt,需要读取 transcript_path 指向的文件才能获取。
子代理的 transcript 在哪里?
子代理的 transcript 存储在主会话目录的 subagents/ 子目录中,通过 agent_transcript_path 可以在 hook 中获取其路径。
十三、社区工具
| 工具 | 功能 |
|---|---|
| claude-code-transcripts | Python CLI,将 transcript 转换为可分享的 HTML 页面 |
| lm-assist | Web UI,可视化浏览所有会话、代理树、Token 成本 |
| ccrider | transcript 格式研究和解析工具 |
| claude-code-ui | 实时看板,跨仓库追踪 Claude Code 会话 |
十四、总结
transcript.jsonl 是 Claude Code 的"飞行记录仪",是理解 Claude 行为、调试问题、审计操作、复用知识的核心数据源。
关键要点:
- 格式:JSONL,每行一个 JSON,纯追加写入
- 结构:通过
parentUuid形成 DAG 图,支持分支对话和并发子代理 - 七种消息类型:
user、assistant、system、tool_result、summary、result、file-history-snapshot - 完整记录:工具调用的输入和输出、扩展思考过程、token 使用量全部保存
- hook 集成:所有 hook 都通过
transcript_path字段提供 transcript 文件路径 - 可编程:标准 JSON 格式,用
jq、Python 等工具即可解析和分析