Skip to content

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.jsonl

history.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": { ... }
}

公共字段说明

字段类型说明
typestring消息类型:userassistantsystemtool_resultsummaryresultfile-history-snapshotqueue-operation
uuidstring本条消息的唯一标识(UUID)
parentUuidstring | null父消息的 UUID,构成有向无环图(DAG)结构
timestampstringISO 8601 时间戳
sessionIdstring会话 ID
cwdstring消息产生时的当前工作目录
versionstringClaude Code 版本号
gitBranchstring当前 Git 分支(不在仓库中为空字符串)
isSidechainboolean是否为侧链消息(分支对话)
userTypestring通常为 "external"
messageobject消息体,结构因 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_resultUserPromptSubmit hook 注入的上下文
system_caveatClaude 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)

类型字段说明
texttextClaude 给你的可见文字回复
tool_useid, name, input工具调用。包含工具名、唯一 ID、完整输入参数。每次 Bash 命令、文件读写、Web 搜索、MCP 调用、子代理创建都会记录
thinkingthinking扩展思考。Claude 的内部推理过程,展示了它为什么做出每个决定。只在启用了扩展思考的模型(如 Opus)上出现

usage — Token 使用量

每个 assistant turn 都包含精确的 token 计量:

字段说明
input_tokens输入 token 数
output_tokens输出 token 数
cache_read_input_tokens缓存命中的 token 数(节省成本)
cache_creation.ephemeral_5m_input_tokens5 分钟缓存写入
cache_creation.ephemeral_1h_input_tokens1 小时缓存写入

stop_reason — 停止原因

含义
end_turnClaude 自然结束回复
tool_useClaude 需要调用工具,等待工具结果后继续
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 执行摘要),包含以下额外字段:

字段说明
hookCounthook 执行数量
hookInfoshook 详细信息
hookErrorshook 错误信息
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子代理会话代理类型(如 ExploreBash

团队模式

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 -10

9.2 查看会话中的消息类型分布

bash
cat session.jsonl | jq -r '.type' | sort | uniq -c | sort -rn

9.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 messages

9.4 统计工具调用次数

bash
cat session.jsonl | jq -r '
  select(.type == "assistant") |
  .message.content[]? |
  select(.type == "tool_use") |
  .name
' | sort | uniq -c | sort -rn

9.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 命令的工作流程:

  1. 识别当前项目 — 根据当前工作目录
  2. 扫描文件 — 列出 ~/.claude/projects/<编码路径>/ 下所有 .jsonl 文件
  3. 按修改时间排序 — 最近修改的排在前面
  4. 展示选择 — 显示 summary 行中的标题和时间
  5. 加载会话 — 选择后顺序读取 JSONL 文件,重建完整对话上下文
  6. 恢复状态 — 处理每条 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-transcriptsPython CLI,将 transcript 转换为可分享的 HTML 页面
lm-assistWeb UI,可视化浏览所有会话、代理树、Token 成本
ccridertranscript 格式研究和解析工具
claude-code-ui实时看板,跨仓库追踪 Claude Code 会话

十四、总结

transcript.jsonl 是 Claude Code 的"飞行记录仪",是理解 Claude 行为、调试问题、审计操作、复用知识的核心数据源。

关键要点:

  • 格式:JSONL,每行一个 JSON,纯追加写入
  • 结构:通过 parentUuid 形成 DAG 图,支持分支对话和并发子代理
  • 七种消息类型userassistantsystemtool_resultsummaryresultfile-history-snapshot
  • 完整记录:工具调用的输入和输出、扩展思考过程、token 使用量全部保存
  • hook 集成:所有 hook 都通过 transcript_path 字段提供 transcript 文件路径
  • 可编程:标准 JSON 格式,用 jq、Python 等工具即可解析和分析